Cod sursa(job #3289621)

Utilizator ShAwDoRneYNacu Gabriel ShAwDoRneY Data 27 martie 2025 17:47:44
Problema Cuplaj maxim in graf bipartit Scor 100
Compilator cpp-64 Status done
Runda Arhiva educationala Marime 7.32 kb
#include <iostream>
#include <fstream>
#include <vector>
#include <queue>
#include <limits>

using namespace std;

// Reprezintă "infinitul" ca o valoare foarte mare.
const int INF = numeric_limits<int>::max();

// Variabile globale pentru dimensiunile grafului și numărul de muchii.
// leftCount  - numărul de noduri din mulțimea stângă (L)
// rightCount - numărul de noduri din mulțimea dreaptă (R)
// edgeCount  - numărul total de muchii între L și R
int leftCount, rightCount, edgeCount;

// leftAdj: pentru fiecare nod din L (indexat de la 1 la leftCount),
// se păstrează o listă cu vecinii din R (nodurile la care este conectat).
vector<vector<int>> leftAdj;

// distanceLeft: vectorul care va reține "distanța" (nivelul) fiecărui nod din L,
// calculată în etapa BFS (Breadth-First Search) pentru a construi graful nivelat.
vector<int> distanceLeft;

// pairLeft și pairRight: vectorii care păstrează cuplajul curent.
// pairLeft[u] este nodul din R cu care este cuplat nodul u din L (0 dacă nu este cuplat).
// pairRight[v] este nodul din L cu care este cuplat nodul v din R (0 dacă nu este cuplat).
vector<int> pairLeft, pairRight;

//
// --- Funcția BFS ---
//
// Scopul acestei funcții este de a construi un graf nivelat începând
// de la toate nodurile libere (necuplate) din L. Pentru fiecare nod din L
// se calculează "distanța" (nivelul) de la un nod liber. Această informație
// este esențială pentru ca în etapa DFS să căutăm doar drumuri care respectă
// această structură (trecerea de la nivelul d la nivelul d+1).
//
bool bfs() {
    queue<int> q;
    
    // Inițializare: pentru fiecare nod din L, dacă este necuplat, 
    // setăm distanța la 0 și îl adăugăm în coadă.
    for (int leftNode = 1; leftNode <= leftCount; leftNode++) {
        if (pairLeft[leftNode] == 0) {
            distanceLeft[leftNode] = 0;
            q.push(leftNode);
        } else {
            // Dacă nodul este cuplat, nu-l considerăm în această etapă.
            distanceLeft[leftNode] = INF;
        }
    }
    
    // Setăm distanța pentru nodul "dummy" 0 la INF.
    // Nodul 0 reprezintă un nod fictiv ce marchează sfârșitul drumurilor.
    distanceLeft[0] = INF;
    
    // Parcurgem în lățime (BFS) graful nivelat.
    while (!q.empty()) {
        int leftNode = q.front();
        q.pop();
        
        // Dacă nodul curent nu este nodul dummy, explorăm vecinii săi.
        
            // Pentru fiecare nod din R adiacent nodului leftNode
            for (int rightNode : leftAdj[leftNode]) {
                // Verificăm cu ce nod din L este cuplat rightNode
                int pairedLeft = pairRight[rightNode];
                // Dacă rightNode este liber, înseamnă că am ajuns la capătul unui drum augmentant.
                if (pairedLeft == 0) {
                    distanceLeft[0] = distanceLeft[leftNode] + 1;
                }
                // Dacă rightNode este cuplat și nodul din L cu care este cuplat nu a fost vizitat,
                // actualizăm distanța și îl adăugăm în coadă.
                else if (distanceLeft[pairedLeft] == INF) {
                    distanceLeft[pairedLeft] = distanceLeft[leftNode] + 1;
                    q.push(pairedLeft);
                }
            }
        
    }
    
    // Dacă distanța pentru nodul dummy s-a schimbat, înseamnă că există cel puțin un drum augmentant.
    return distanceLeft[0] != INF;
}

//
// --- Funcția DFS ---
//
// Această funcție încearcă să găsească un drum augmentant în graful nivelat,
// pornind de la un nod din L. Se respectă nivelurile calculate în etapa BFS,
// adică se merge de la un nod la un vecin care se află la nivelul curent + 1.
// Dacă se găsește un drum care duce la un nod liber (nod dummy 0), se actualizează
// cuplajele (pairLeft și pairRight) de-a lungul drumului.
//
bool dfs(int leftNode) {

        // Parcurgem fiecare vecin din R al nodului leftNode
        for (int rightNode : leftAdj[leftNode]) {
            int pairedLeft = pairRight[rightNode];
            // Dacă rightNode este liber sau dacă putem continua drumul augmentant
            // de la nodul din L cu care este cuplat rightNode, respectând nivelul,
            // atunci actualizăm cuplajul.
            if (pairedLeft == 0 || (distanceLeft[pairedLeft] == distanceLeft[leftNode] + 1 && dfs(pairedLeft))) {
                pairLeft[leftNode] = rightNode;
                pairRight[rightNode] = leftNode;
                return true;
            }
        }
        // Dacă nu se găsește niciun drum augmentant pornind din leftNode,
        // marcăm nodul cu INF pentru a nu-l reexamina.
        distanceLeft[leftNode] = INF;
        return false;
    
}

//
// --- Funcția hopcroftKarp ---
//
// Funcția principală care găsește cuplajul maxim.
// Se repetă alternanța dintre construcția grafului nivelat (BFS)
// și căutarea drumurilor augmentante (DFS). Pentru fiecare drum găsit,
// se mărește numărul total de cuplaje.
//
int hopcroftKarp() {
    int matching = 0;
    
    // Cât timp există drumuri augmentante (BFS returnează true),
    // încercăm să le găsim cu DFS.
    while (bfs()) {
        // Pentru fiecare nod din L necuplat, încercăm să găsim un drum augmentant.
        for (int leftNode = 1; leftNode <= leftCount; leftNode++) {
            if (pairLeft[leftNode] == 0 && dfs(leftNode))
                matching++;
        }
    }
    return matching;
}

//
// --- Funcția main ---
//
// Aceasta citește graful din fișierul "cuplaj.in", inițializează variabilele necesare,
// apelează algoritmul Hopcroft-Karp pentru a calcula cuplajul maxim și scrie rezultatul în "cuplaj.out".
//
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);

    ifstream fin("cuplaj.in");
    ofstream fout("cuplaj.out");
    if (!fin || !fout) {
        cerr << "Error opening input/output files!" << endl;
        return 1;
    }
    
    // Citim numărul de noduri din L, din R și numărul de muchii
    fin >> leftCount >> rightCount >> edgeCount;
    
    // Inițializăm lista de adiacență pentru nodurile din L
    leftAdj.resize(leftCount + 1);
    
    // Inițializăm vectorii de cuplaje cu 0 (nodurile sunt inițial necuplate)
    pairLeft.assign(leftCount + 1, 0);
    pairRight.assign(rightCount + 1, 0);
    
    // Inițializăm vectorul de distanțe cu INF
    distanceLeft.assign(leftCount + 1, INF);
    
    // Citim fiecare muchie și construim lista de adiacență.
    // Fiecare muchie este dată printr-un nod u din L și un nod v din R.
    for (int i = 0; i < edgeCount; i++) {
        int u, v;
        fin >> u >> v;
        if (u >= 1 && u <= leftCount && v >= 1 && v <= rightCount)
            leftAdj[u].push_back(v);
    }
    
    // Calculăm cuplajul maxim folosind algoritmul Hopcroft-Karp.
    int maxMatching = hopcroftKarp();
    
    // Scriem numărul maxim de cuplaje și apoi fiecare pereche (u din L, v din R)
    fout << maxMatching << "\n";
    for (int leftNode = 1; leftNode <= leftCount; leftNode++) {
        if (pairLeft[leftNode] != 0)
            fout << leftNode << " " << pairLeft[leftNode] << "\n";
    }
    
    fin.close();
    fout.close();
    return 0;
}