比起题解区的神奇构造,我更喜欢@约瑟夫用脑玩
的思维模式,能够推广到更加普适的问题,为图论解题提供启发,我们接下来讲述一下这种做法。
考虑到这是个 DAG,我们不妨对每个点设一个深度 ,然后强制在删完后的图中,边 等价于 ,那么原限制等价于,不存在
的点。
考察特殊对象是 OI 中的常见手段,我们在这道题中,首先考察入度为 的点,那么其深度为 。
深度为
的点指出去的点,要么深度为 ,要么就被删掉。
而深度为
的点指出去的点,一定要被删掉。
因为我们要删掉的尽量少,一个点肯定不能随便成为深度为 的点。
如果一个点,比它拓扑序小的都确定了,且有深度为 的点指向它,那么它就只能是深度为 的点。
因为深度为
的点等价于被删掉,所以,如果一个点的入边都是深度为 的点,它和深度为 的点是等效的。
按照上面的规则,按拓扑序更新 即可。
设 的点的个数为 。
因为出度 ,所以
容易发现 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| #include <bits/stdc++.h> using namespace std; const int N = 2e5 + 5; int n,m; vector<int> G[N]; int dep[N],deg[N]; queue<int> Q; inline void Work() { cin >> n >> m; for(int i = 1;i <= n;i++) dep[i] = deg[i] = 0,G[i].clear(); for(int i = 1;i <= m;i++) { int x,y; cin >> x >> y; G[x].push_back(y);++deg[y]; } for(int i = 1;i <= n;i++) if(!deg[i]) Q.push(i),dep[i] = 1; while(!Q.empty()) { int x = Q.front();Q.pop(); for(auto y : G[x]) { --deg[y]; if(dep[x] != -1) { if(dep[x] == 1 && dep[y] != -1) dep[y] = 2; else dep[y] = -1; } if(!deg[y]) {Q.push(y); if(dep[y] == 0) dep[y] = 1;} } } vector<int> Ans; for(int i = 1;i <= n;i++) if(dep[i] == -1) Ans.push_back(i); printf("%lu\n",Ans.size()); for(auto i : Ans) printf("%d ",i); printf("\n"); } int main() { int T; cin >> T; while(T--) Work(); return 0; }
|