[APIO2009]抢掠计划(强连通分量+缩点+拓扑排序+dp)

题意:

给定一个有向图,从指定起点出发,到任意一个指定终点停止,求经过的所有结点的最大点权和。点数、边数<=500000


因为一个强连通分量内的点相互可达,所以如果要经过其中一个点,就应经过它所在的强连通分量内的所有点,因此将一个强连通分量缩成一个点 
这样得到了一个有向无环图,在图上dp即可 

先对所有起点可达的点做一遍拓扑排序,然后用队列维护入度为0的点,每次取出队首元素,用它的答案更新其他点即可 


好像有一个点会RE?


#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int u[500005],v[500005],w[500005],first[500005],next[500005],bar[500005],f[500005],d[500005],vis[500005];
int pre[500005],link[500005],sta[500005],sccno[500005],q[500005];
int clo=0,top=0;
void find(int x)
{
	int i,y;
	pre[x]=link[x]=++clo;
	sta[++top]=x;
	for(i=first[x];i!=0;i=next[i])
		if(sccno[v[i]]==0)
		{
			if(pre[v[i]]==0) find(v[i]);
			if(link[x]>link[v[i]]) link[x]=link[v[i]];
		}
	if(pre[x]==link[x])
	{
		while(sta[top]!=x) sccno[sta[top--]]=x;
		sccno[sta[top--]]=x;
	}
}
void dfs(int x)
{
	int i;
	vis[x]=1;
	for(i=first[x];i!=0;i=next[i])
		if(vis[v[i]]==0) dfs(v[i]);
}
int main()
{
	int n,m,s,p,i,head=0,tail=0,max=0;
	scanf("%d%d",&n,&m);
	for(i=1;i<=m;i++)
	{
		scanf("%d%d",&u[i],&v[i]);
		next[i]=first[u[i]];
		first[u[i]]=i;
	}
	for(i=1;i<=n;i++)
		scanf("%d",&w[i]);
	scanf("%d%d",&s,&p);
	for(i=1;i<=p;i++)
		scanf("%d",&bar[i]);
	for(i=1;i<=n;i++)
		if(sccno[i]==0) find(i);//计算强连通分量 
	memset(first,0,sizeof(first));
	memset(next,0,sizeof(next));
	for(i=1;i<=m;i++)//缩点 
	{
		u[i]=sccno[u[i]];
		v[i]=sccno[v[i]];
		if(u[i]!=v[i])
		{
			next[i]=first[u[i]];
			first[u[i]]=i;
		}
	}
	for(i=1;i<=n;i++)
		if(sccno[i]!=i) w[sccno[i]]+=w[i];
	dfs(sccno[s]);
	for(i=1;i<=m;i++)
		if(u[i]!=v[i]&&vis[u[i]]==1) f[v[i]]++;
	q[tail++]=sccno[s];
	while(head<tail)
	{
		d[q[head]]+=w[q[head]];
		for(i=first[q[head]];i!=0;i=next[i])
		{
			if(d[v[i]]<d[u[i]]) d[v[i]]=d[u[i]];
			f[v[i]]--;
			if(f[v[i]]==0) q[tail++]=v[i];
		}
		head++;
	}
	for(i=1;i<=p;i++)
		if(max<d[sccno[bar[i]]]) max=d[sccno[bar[i]]];
	printf("%d",max);
	return 0;
}


你可能感兴趣的:(dp,拓扑排序,apio,tarjan算法,缩点)