• 浅谈缩点


    前置芝士:Tarjan求强连通分量

    对于一个有向图中的两个点,对于(V_i->V_j)有一条边且(V_j->V_i)有一条边(即能互相到达),就是一个强连通分量(不局限于两个点)

    我们可以用(Tarjan)求出一个有向图中所有的强连通分量。

    那么,在一些图中可以将强连通分量缩成一个点。并对它做一个标记。

    例题:(Luogu) (P3387)

    对于这道题,我们求路径的时候,在一个强连通分量重,既然可以互相到达,而且图中求的是最大的路径,所以我们直接将它缩成一个点,累计其中的权值和即可。

    缩点时,我们可以做一个标记,将一个环标记为一个点,为了方便就取(Tarjan)中强连通分量重遇到的第一个点(栈底的那个)作为编号。

    那么我们连边的时候,就可以多记录一下一条边的两端的点。缩完点的时候,重新连一遍图,这时候我们维护的就需要用到了。

    对于原来两条边的点,如果不在一个强连通分量中,就链接一遍。

    怎么求最大路径?

    我们设(dis[i])是以(i)为起点的最长路径,则:

    遍历一遍点(i)连的点(j),则:

    (dis[j]=max(dis[j],dis[i]+val[j]))(val[j])为j所在环内的权值和(或者说(j)的点权)

    最后对所有点取一遍最大值即可。

    (Code:)

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    using namespace std;
    const int MAXN=500000;
    int id,si[MAXN],head[MAXN],n,m,cnt,tot,val[MAXN],count,h[MAXN];
    int low[MAXN],dfn[MAXN],top,st[MAXN],inst[MAXN],in[MAXN],dis[MAXN];
    struct edge{
    	int nxt,to,pre;
    }e[MAXN],g[MAXN];
    inline void add(int x,int y){
    	e[++tot].nxt=head[x];
    	e[tot].to=y;
    	e[tot].pre=x;//记录边的两点 
    	head[x]=tot;
    }
    inline void kdd(int x,int y){
    	g[++count].nxt=h[x];
    	g[count].to=y;
    	g[count].pre=x;//同上 
    	h[x]=count;
    }
    void Tarjan(int x){
    	st[++top]=x;low[x]=dfn[x]=++id;inst[x]=1;
    	for(int i=head[x];i;i=e[i].nxt){
    		int j=e[i].to;
    		if(!dfn[j]){
    			Tarjan(j);
    			low[x]=min(low[x],low[j]);
    		}
    		else if(inst[j])low[x]=min(low[x],dfn[j]);
    	}//正常Tarjan求强连通分量 
    	if(low[x]==dfn[x]){
    		int y;
    		while(y=st[top--]){
    			si[y]=x;//这里是把这个环都标记为x 
    			inst[y]=0;//弹出栈了就标记一下 
    			if(x==y)break;//找完了就跳出 
    			val[x]+=val[y];//累计环内权值 
    		}
    	}
    }
    int solve(){
    	queue<int>q;//队列维护一下 
    	for(int i=1;i<=n;++i){
    		if(si[i]==i&&!in[i]){//对于一个点如果它是这个环的编号且入度为0 
    			q.push(i);//加入队列 
    			dis[i]=val[i];//初始当然是环内权值和 
    		}
    	}
    	while(!q.empty()){//如果队列非空就继续 
    		int k=q.front();q.pop();//队头,弹出 
    		for(int i=h[k];i;i=g[i].nxt){//链式前向星 
    			int j=g[i].to;
    			dis[j]=max(dis[j],dis[k]+val[j]);//对于一个点,能连接的就更新一下 
    			in[j]--;//j更新完去掉一个入度 
    			if(!in[j])q.push(j);//拓扑排序,可以了就入队 
    		}
    	}
    	int Ans=0;//取Max做答案 
    	for(int i=1;i<=n;++i)Ans=max(Ans,dis[i]);
    	return Ans;//完结撒花 
    }
    int main(){
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;++i)scanf("%d",&val[i]);
    	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(!dfn[i])Tarjan(i);//每一个没有遍历到的点Tarjan一遍 
    	for(int i=1;i<=m;++i){
    		int ii=e[i].pre,jj=e[i].to;//边的两端 
    		int x=si[ii],y=si[jj];//对于每一条边求出它们所在的环 
    		if(x!=y){//不在同一条边就连边 
    			kdd(x,y);//缩完点之后重新连边 
    			in[y]++;//入度++ 
    		}
    	}
    	printf("%d
    ",solve());
    	return 0;
    }
    
  • 相关阅读:
    Python-深浅拷贝
    Python-生成式
    Python-手写web应用
    Python-为什么产生了GIL锁?
    Python-文件处理
    Python-线程
    10大网站设计错误 足以毁掉你的网站【转】
    [转]ASP.NET验证发生前无法调用 Page.IsValid。应在 CausesValidation=True 且已启动回发的控件
    jquery操作字符串常用方法总结及工作代码
    C#中的序列化和反序列化是什么、有什么作用、使用方法详解
  • 原文地址:https://www.cnblogs.com/h-lka/p/11366595.html
Copyright © 2020-2023  润新知