题目
题目链接:https://codeforces.com/problemset/problem/1019/C
给定一张无自环的有向图,选出一个点集 \(S\),使得:
- \(S\) 中任意两个点的距离不小于 \(2\)。
- 对于任意不在 \(S\) 中的点 \(x\),存在一个 \(S\) 中的点 \(y\) 使得 \(y\) 到 \(x\) 的距离不大于 \(2\)。
输出任意一中解。
\(n,m\leq 10^6\)。
思路
直接给解法:
从小到大枚举所有点,如果这个点没有被标记,那么把这个点能一步到达的所有编号大于它的点标记。
再从大到小枚举所有点,如果这个点没有被标记,那么把这个点能一步到达的所有编号小于它的点标记。
最后没有被标记的点就是一个合法解。
证明:
记第一次被标记的点集为 \(S_1\),第二次被标记的点集为 \(S_2\),最后没有被标记的点集为 \(S\)。
\(S\) 中任意两个点距离肯定是不小于 \(2\) 的。否则被到达的点一定会被标记上。
而 \(S_2\) 中每一个点都可以从 \(S\) 其中一个点一步到达,\(S_1\) 中每一个点都可以从 \(S\) 或 \(S_2\) 其中一个点一步到达,也就是 \(S_1\cup S_2\) 中所有点都可以通过 \(S\) 两步内到达。
时间复杂度 \(O(n+m)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=1000010;
int n,m,tot,head[N];
bool vis[N];
struct edge
{
int next,to;
}e[N];
void add(int from,int to)
{
e[++tot]=(edge){head[from],to};
head[from]=tot;
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for (int i=1,x,y;i<=m;i++)
{
scanf("%d%d",&x,&y);
add(x,y);
}
for (int i=1;i<=n;i++)
if (!vis[i])
for (int j=head[i];~j;j=e[j].next)
if (e[j].to>i) vis[e[j].to]=1;
for (int i=n;i>=1;i--)
if (!vis[i])
for (int j=head[i];~j;j=e[j].next)
if (e[j].to<i) vis[e[j].to]=1;
tot=0;
for (int i=1;i<=n;i++)
if (!vis[i]) tot++;
cout<<tot<<"\n";
for (int i=1;i<=n;i++)
if (!vis[i]) cout<<i<<" ";
return 0;
}