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;
}