CF1368E 题解

First Post:

Last Update:

比起题解区的神奇构造,我更喜欢@约瑟夫用脑玩 的思维模式,能够推广到更加普适的问题,为图论解题提供启发,我们接下来讲述一下这种做法。

考虑到这是个 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;
// else dep[i] = 1e9;
while(!Q.empty()) // dp = -1 表示已经被删除;dp = 0 表示所有入边都是被删的点,我们在入队时会把这种情况置为 1
{
int x = Q.front();Q.pop();
// printf("dep[%d]=%d\n",x,dep[x]);
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;
}