Codeforces 1019C Sergey's problem 构造
原文链接https://www.cnblogs.com/zhouzhendong/p/CF1019C.html
题目传送门 - CF1019C
题意
给定一个有 $n$ 个节点 、 $m$ 条边的有向图,没有自环,但是可能存在环。
现在要求选出一个点集满足一下条件。
设原来的所有点构成的点集为 $V$ ,选出的点集为 $S$,则:
1. 对于所有满足 $x,y\in S$ 的点 $x,y$ ,有向边 $(x,y)$ 不存在。
2. 对于所有满足 $y\in V$ 的点,都可以找到一个点 $x(x\in S)$,满足从点 $x$ 开始走到 $y$ 的最少经过边数不超过 2 。
首先输出你选出的点数,然后按照编号从小到大输出你选的点。
$n,m\leq 10^6$
题解
我们考虑以下构造方案:
1. 记当前的图为 $G(V,E)$ 。
2. 选择一个节点 $A\in V$ ,从 $G$ 中删除节点 $A$ ,以及从 $A$ 出发的有向边连向的所有节点,得到新图 $G^\prime$ 。
3. 如果 $V^\prime \neq \emptyset $ ,则返回第 1. 步。否则到第 4 步。
4. 记之前选出的所有节点 $A$ 构成的集合为 $v$ ,取 $v$ 和 原图 $G$ 中只与 $v$ 中的点有关边集 $e$ ,构成新图 $g(v,e)$ 。容易得知,$g$ 是一个有向无环图。
5. 记当前的图为 $g(v,e)$ 。
6. 取一个入度为 $0$ 的节点 $a$ ,并将该节点加入答案集合 $S$,删除 $a$ 以及在 $g$ 中 $a$ 能一步走到的所有点。设得到的新图的点集为 $v^\prime$ 。
7. 如果 $v^\prime \neq \emptyset$ ,则返回第 1. 步。否则输出答案集合 $S$ 。
现在简单的说明一下这样做的正确性:
① 首先,显然任意两个属于答案集合点不能一步到达。
② 对于任意满足 $x\in v,x\notin S$ 的节点 $x$ ,它只可能在第 6 步被删除,那么,显然有一个能一步达到 $x$ 的节点被记入答案。
③ 对于任意满足 $x\in V,x\notin v$ 的节点 $x$ ,它只可能在第 2 步的时候被删除,那么,显然有一个能一步到达 $x$ 的节点 $y$ 在集合 $v$ 中。又根据 ② ,如果 $y\notin S$ ,有一步到 $y$ 的节点,则 $x$ 可以花两步到达;否则,$y\in S$ ,$x$ 可以由 $y$ 一步到达。
④ 由于属于答案集合的节点显然可以在 2 步以内到达,再根据 ②③ ,上述做法的正确性显然。
接下来就只差一个方便的实现方法了,详见代码。
代码
#include <bits/stdc++.h> using namespace std; typedef long long LL; const int N=1000005; int read(){ int x=0; char ch=getchar(); while (!isdigit(ch)) ch=getchar(); while (isdigit(ch)) x=(x<<1)+(x<<3)+ch-48,ch=getchar(); return x; } void write(int v){ int k=v/10; if (v>9) write(k); putchar('0'+(v-k*10)); } struct Gragh{ static const int M=1000005; int cnt,y[M],nxt[M],fst[N]; void clear(){ cnt=0; memset(fst,0,sizeof fst); } void add(int a,int b){ y[++cnt]=b,nxt[cnt]=fst[a],fst[a]=cnt; } }g; int n,m; int vis[N],ans[N],anscnt=0; int main(){ n=read(),m=read(); g.clear(); for (int i=1;i<=m;i++){ int a=read(),b=read(); g.add(a,b); } for (int i=1;i<=n;i++) if (!vis[i]){ vis[i]=-2; for (int j=g.fst[i];j;j=g.nxt[j]) vis[g.y[j]]=min(vis[g.y[j]],-1); } for (int i=n;i>=1;i--) if (vis[i]==-2){ ans[++anscnt]=i; for (int j=g.fst[i];j;j=g.nxt[j]) vis[g.y[j]]=-1; } write(anscnt),puts(""); for (int i=anscnt;i>=1;i--) write(ans[i]),putchar(' '); return 0; }