【XSY3325】社保(拓扑序)

这篇博客探讨了如何利用图论中的拓扑排序和有向无环图(DAG)解决特定问题。通过分析DAG的性质,提出了一种寻找特定节点的方法,即寻找那些在DAG及其反向图中都能到达所有节点的点。文章详细描述了两种思路,并通过tarjan算法进行拓扑排序,最终实现问题的解决方案。在实际编程实现中,使用了邻接表存储图结构,并进行了高效的数据结构操作。
摘要由CSDN通过智能技术生成

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

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

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

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

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

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

我们可以同样考虑哪些点走不到 u u 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
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值