浅谈缩点
Posted h-lka
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈缩点相关的知识,希望对你有一定的参考价值。
前置芝士: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\n",solve());
return 0;
以上是关于浅谈缩点的主要内容,如果未能解决你的问题,请参考以下文章