【XSY3325】社保(拓扑序)

显然我们先缩点,之后转化为一个 DAG,设为 \(G\),设由其反边构成的图为 \(G'\)。题意就是求所有 “好的” 点,其中一个 “好的” 点需要满足这个点在 \(G\) 上能走到的点和在 \(G'\) 上能走到的点的并集为所有点。

思路 1:显然一个点不可能同时在 \(G\)\(G'\) 上都能被同一个点 \(u\) 走到(除了这个点就是 \(u\)),于是我们可以在 \(G\)\(G'\) 上分别求出 \(u\) 能走到的点的个数,再加起来判断是否等于 \(n+1\)。但你发现这个能走到的点的个数并不好快速求。(如果用 bitset+拓扑 那么与直接暴力无异)

我们可以将限制改的更严一点:我们先对 \(G\) 随便跑一个拓扑序出来,假设点 \(u\) 的拓扑序为 \(a_u\)。那么一个点 \(u\) 如果是 “好的” 点,当且仅当:在 \(G\) 上,拓扑序比 \(u\) 小的点都能走到 \(u\),拓扑序比 \(u\) 大的点 \(u\) 都能走到。

思路 2:注意到这个条件其实告诉了我们一个隐含的信息:某个 “好的” 点 \(u\) 一定满足 \(u\) 在拓扑序的位置唯一。但是在考场上并没有想出来按这个思路做应该怎么做。

我们还是回到原来的要求:一个点 \(u\) 如果是 “好的” 点,当且仅当:在 \(G\) 上,拓扑序比 \(u\) 小的点都能走到 \(u\),拓扑序比 \(u\) 大的点 \(u\) 都能走到。

对于一个点 \(u\),我们可以先考虑哪些点 \(u\) 走不到,那么 \(u\) 走不到的点肯定不是 “好的” 点。我们可以找出 \(u\) 的所有儿子中拓扑序最小的那个点 \(v\),那么拓扑序在 \((a_u,a_v)\) 之间的点 \(u\) 肯定走不到,那么我们对区间 \((a_u,a_v)\) 打上标记。

我们可以同样考虑哪些点走不到 \(u\)

最后可以证明没有被打上标记的点一定是 ”好的“ 点。

#include<bits/stdc++.h>

#define N 1000010

using namespace std;

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^'0');
		ch=getchar();
	}
	return x*f;
}

int n,m;
int cnt=1,head[N],nxt[N<<1],to[N<<1];
int idx,dfn[N],low[N];
int top,sta[N];
int nscc,scc[N];
int num,du[N],a[N];
int c[N];
bool ins[N];
bool cor[N];
bool F;

vector<int>e[N];

void tarjan(int u)
{
	dfn[u]=low[u]=++idx;
	sta[++top]=u,ins[u]=1;
	for(int v:e[u])
	{
		if(!dfn[v])
		{
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(ins[v]) low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u])
	{
		nscc++;
		int v;
		do
		{
			v=sta[top],top--;
			ins[v]=0;
			scc[v]=nscc;
		}while(v!=u);
	}
}

void adde(int u,int v)
{
	to[++cnt]=v;
	nxt[cnt]=head[u];
	head[u]=cnt;
}

queue<int>q;

void tuopu()
{
	num=0;
	for(int i=1;i<=nscc;i++)
		if(!du[i]) q.push(i);	
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		a[u]=++num;
		for(int i=head[u];i;i=nxt[i])
		{
			if((i&1)^F) continue;
			int v=to[i];
			du[v]--;
			if(!du[v]) q.push(v);
		}
	}
}

void work()
{
	for(int i=1;i<=nscc;i++) du[i]=0;
	for(int i=2+F;i<=cnt;i+=2) du[to[i]]++;
	tuopu();
	if(num!=nscc)
	{
		puts("0");
		exit(0);
	}
	for(int i=1;i<=nscc;i++) c[i]=0;
	for(int u=1;u<=nscc;u++)
	{
		int minv=0;
		for(int i=head[u];i;i=nxt[i])
		{
			if((i&1)^F) continue;
			if(!minv||a[to[i]]<a[minv]) minv=to[i];
		}
		if(minv) c[a[u]+1]++,c[a[minv]]--;
		else c[a[u]+1]++,c[nscc+1]--;
		int maxv=0;
		for(int i=head[u];i;i=nxt[i])
		{
			if(!((i&1)^F)) continue;
			if(!maxv||a[to[i]]>a[maxv]) maxv=to[i];
		}
		if(maxv) c[a[maxv]+1]++,c[a[u]]--;
		else c[1]++,c[a[u]]--;
	}
	for(int i=1;i<=nscc;i++) c[i]+=c[i-1];
	for(int i=1;i<=nscc;i++) if(c[a[i]]) cor[i]=0;
}

int main()
{
	n=read(),m=read();
	for(int i=1;i<=m;i++)
	{
		int u=read(),v=read();
		e[u].push_back(v);
	}
	for(int i=1;i<=n;i++)
		if(!dfn[i]) tarjan(i);
	for(int u=1;u<=n;u++)
	{
		for(int v:e[u])
		{
			if(scc[u]!=scc[v])
			{
				adde(scc[u],scc[v]);
				adde(scc[v],scc[u]);
			}
		}
	}
	for(int i=1;i<=nscc;i++) cor[i]=1;
	F=0;work();
	vector<int>ans;
	ans.clear();
	for(int i=1;i<=n;i++)
		if(cor[scc[i]]) ans.push_back(i);
	printf("%d\n",(int)ans.size());
	for(int u:ans) printf("%d ",u);
	return 0;
}
/*
6 7
1 2
1 3
2 4
3 4
4 5
5 6
6 5
*/
posted @ 2022-10-30 11:01  ez_lcw  阅读(14)  评论(0编辑  收藏  举报