Sytuację w zadaniu można zamodelować grafem skierowanym o
Ponieważ z każdego wierzchołka wychodzi dokładnie jedna skierowana krawędź, nasz graf ma specjalną postać: każda jego słabo spójna składowa jest skierowanym cyklem (być może długości 1), do którego mogą być podczepione skierowane drzewa.
Na początek zauważamy, że jeśli
Wierzchołki na wejściu podane są nie w postaci numerów, ale w postaci napisów.
Na początek wygodnie nam będzie zamienić je na liczby od
const int N = 110000; int n,par[N],vis[N]; char s1[11],s2[11]; map<ll,int> m; vector<int> adjT[N]; int encode(const char* s) { ll val = 0; while (*s) { val = 27*val + *s++ - 'a'+1; } if (!m.count(val)) { int s = m.size(); m[val] = s; } return m[val]; } int main() { scanf("%d",&n); if (n%2) { printf("-1\n"); return 0; } REP(i,n) { scanf("%s%s",s1,s2); int v1 = encode(s1), v2 = encode(s2); par[v1] = v2; if (v1 != v2) adjT[v2].push_back(v1); }
W podzadaniu 2 mamy dodatkowy warunek, że „każdy jest przez kogoś kochany”,
to znaczy, że do każdego wierzchołka wchodzi co najmniej jedna krawędź.
Ponieważ krawędzi jest
Zatem graf ma jeszcze prostszą postać: składa się ze skierowanych cykli (być może długości 1).
Rozważmy pojedynczy cykl. Jeśli ma on długość 1, to musimy zmienić krawędź wychodzącą z jego pojedynczego wierzchołka. Jeśli ma on długość 2, to nie musimy nic robić.
Niech więc cykl ten ma długość
Okazuje się, że można uzyskać taki wynik.
Jeśli
Wystarczy zatem dla każdego cyklu wyznaczyć jego długość
int ans = 0; REP(i,n) if (!vis[i]) { int j = i, len = 0; do { vis[j] = 1; ++len; j = par[j]; } while (j != i); if (len != 2) { ans += (len + 1) / 2; } }
Rozwiązanie działa w czasie
W podzadaniu 3 mamy inny warunek upraszczający postać grafu: nie ma w nim cykli dłuższych niż 1. Zatem każda słabo spójna składowa jest wierzchołkiem-cyklem długości 1, do którego popodczepiane są skierowane drzewa. Można na to równoważnie patrzeć jak na pojedyncze drzewo skierowane, w którego korzeniu mamy cykl.
Skoro mamy optymalizować liczbę zmienionych krawędzi w drzewie, spróbujmy do tego wykorzystać programowanie dynamiczne na drzewie.
Niech
Dla liścia mamy
Dla wierzchołka wewnętrznego
Jeśli krawędź nie zostaje, to albo płacimy
int tak[N],nie[N]; void dfs(int v) { if (adjT[v].empty()) { tak[v] = 0; nie[v] = 1; } else { int sumnie = 0; for (int u : adjT[v]) { dfs(u); sumnie += nie[u]; } tak[v] = sumnie; nie[v] = 1 + sumnie; for (int u : adjT[v]) { nie[v] = min(nie[v], 1 + sumnie - nie[u] + tak[u]); } } }
Wynikiem jest oczywiście suma
int ans = 0; REP(i,n) if (par[i] == i) { dfs(i); ans += nie[i]; }
Całe rozwiązanie działa w czasie
W ogólnym przypadku spójna składowa ma postać cyklu z podoczepianymi drzewami. Możemy sprowadzić ten przypadek do przypadku pojedynczego drzewa. W tym celu wybieramy dowolną krawędź na cyklu i rozważamy dwa przypadki: krawędź zostaje lub nie. W obu z nich rozcinamy cykl na tej krawędzi i uruchamiamy poprzedni algorytm.
Czas działania taki sam: