题解 CF1239D 【Catowice City】

SIGSEGV

2019-10-21 07:56:58

Solution

大致题意:有$n$个人和$n$只猫,每个人都认识编号与他相同的猫和另外一些猫,现在需要从人和猫中选出共$n$个,使得其中既有人,也有猫,并且所有选出的人都不认识任何选出的猫。 思路: 首先,同一个编号的人和猫,必须选且仅选一个。为什么?感性证明一下(可能有误):假设选了$r$个人,$n-r$个猫,如果选中的人和猫的编号的集合有交集,那么这种方案肯定不合法。而所有人和所有猫的集合大小都是$n$且人和猫的编号一一对应,所以没有被选中的人,他对应的猫一定会被选中。 简而言之,选出的人的集合与选出的猫的集合互为补集。 那么考虑人认识猫的情况。假设人$R$认识猫$C$,套用2-SAT的思想,那么选了人$R$就不能选猫$C$,则根据上面的结论,一旦选了$R$就必须选编号为$C$的人。 因此,对于所有的关系,从$R$向编号为$C$的人连一条有向边,缩点后如果所有点在一个强连通分量里则无解,否则选择一个**出度为0**的强连通分量即可。 代码是考场上写的,稍微有点丑: ```cpp #include <bits/stdc++.h> using namespace std; const int N = 2000005; int n,m; vector<int> e[N]; int dfn[N],scc_idx[N],low[N],tim,idx,sz[N],deg[N]; bool vis[N];stack<int> stk; void tarjan(int pos) //Tarjan { dfn[pos] = low[pos] = ++tim;vis[pos] = 1;stk.push(pos); for (auto &i : e[pos]) if (!dfn[i]) tarjan(i),low[pos] = min(low[pos],low[i]); else if (vis[i]) low[pos] = min(low[pos],dfn[i]); if (dfn[pos] == low[pos]) { ++idx; while (!stk.empty()) { int nd = stk.top();stk.pop(); vis[nd] = 0;scc_idx[nd] = idx;++sz[idx]; if (nd == pos) break; } } } int main () { ios::sync_with_stdio(false); int T;cin >> T; while (T--) { cin >> n >> m; tim = idx = 0; int t1,t2; for (int i = 1;i <= n;i++) e[i].clear(); for (int i = 1;i <= m;i++) { cin >> t1 >> t2; if (t1 != t2) e[t1].push_back(t2);//连边 } for (int i = 1;i <= n;i++) scc_idx[i] = sz[i] = dfn[i] = low[i] = deg[i] = vis[i] = 0;//记得多组数据的题的初始化 for (int i = 1;i <= n;i++) if (!dfn[i]) tarjan(i); for (int i = 1;i <= n;i++) for (auto &j : e[i]) if (scc_idx[i] != scc_idx[j]) ++deg[scc_idx[i]]; //计算出度 if (sz[scc_idx[1]] == n) { puts("NO");continue; }//无解情况 bool fl = 0; for (int i = 1;i <= idx;i++) if (!deg[i] && sz[i]) //有解 { puts("YES");fl = 1; vector<int> ans[2]; for (int j = 1;j <= n;j++) ans[scc_idx[j] == i].push_back(j); printf("%d %d\n",ans[1].size(),ans[0].size()); for (auto &i : ans[1]) printf("%d ",i); puts(""); for (auto &i : ans[0]) printf("%d ",i); puts(""); break; } if (!fl) puts("NO"); } return 0; } ```