P3387 Tarjan 缩点
【模板】缩点
题目描述
给定一个 \(n\) 个点 \(m\) 条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
输入格式
第一行两个正整数 \(n,m\)
第二行 \(n\) 个整数,其中第 \(i\) 个数 \(a_i\) 表示点 \(i\) 的点权。
第三至 \(m+2\) 行,每行两个整数 \(u,v\),表示一条 \(u\rightarrow v\) 的有向边。
输出格式
共一行,最大的点权之和。
样例 #1
样例输入 #1
2 2
1 1
1 2
2 1
样例输出 #1
2
提示
对于 \(100\%\) 的数据,\(1\le n \le 10^4\),\(1\le m \le 10^5\),\(0\le a_i\le 10^3\)。
参考注释 + https://www.luogu.com.cn/problem/solution/P3387 第二篇题解
关键是dfn low stack 以及对图的缩点后的重建
#include<bits/stdc++.h>
#define maxn 100001
#define maxm 500001
using namespace std;
struct node{
int to,next,from;
}edge[maxm];
queue<int>q;
vector<int>cb[maxn];
vector<int>rdr[maxn];
int ans[maxn],totq,x,y,v,in[maxn],u,n,m,sum,belong[maxn],dis_[maxn],dis[maxn];
int dfn[maxn],low[maxn],f[maxn],times,cntqq;
int stack_[maxn],head[maxm],visit[maxn],cnt,tot,pt;
void add(int x,int y) //建边
{
++cntqq;
edge[cntqq].next=head[x];
edge[cntqq].from=x;
edge[cntqq].to=y;
head[x]=cntqq;
}
void topsort() //拓扑排序
{
for(int i=1;i<=tot;i++) //初始化
{
if(in[i]==0)
q.push(i); //入度为0的都进队列
}
while(!q.empty())
{
int u=q.front();
q.pop();
ans[++totq]=u;
for(int i=0;i<cb[u].size();i++)
{
v=cb[u][i];
in[v]--;
if(in[v]==0)q.push(v);
}
}
}
void tarjan(int x)//tarjan求强连通分量
{
dfn[x]=low[x]=++times;//dfn[i]:i点的进入时间
//low[i]:从i点出发 所能访问到的最早进入时间
stack_[++pt]=x;
visit[x]=1;//x已访问并入栈
for(int i=head[x];i;i=edge[i].next)
{
if(!dfn[edge[i].to])//未访问
{
tarjan(edge[i].to);
low[x]=min(low[x],low[edge[i].to]);//之所以是两个low 可以理解为 dfs递归
}
else
if(visit[edge[i].to])//已入栈
low[x]=min(low[x],dfn[edge[i].to]);//没有递归 to点的times就是dfn[to]
}
if(low[x]==dfn[x])//说明是强连通分量
{
tot++;//强连通分量编号
while(1)
{
//将强连通分量 内 所有点都belong to 所属强连通分量编号(缩成一个点)
belong[stack_[pt]]=tot; //pt所在的强连通分量编号,等于前面讲的belong
dis_[tot]+=dis[stack_[pt]]; //强连通分量权值累加
visit[stack_[pt]]=0;//标记出栈
pt--;
if(x==stack_[pt+1])break;//若x都已经出栈了 这个强连通分量内的所有点就缩完了 就break
}
}
}
signed main()
{
memset(head,0,sizeof(head));
int n,m,x,y;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&dis[i]);
for(int i=1;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<=cntqq;i++)
{ //拓扑建边
if(belong[edge[i].from]!=belong[edge[i].to])//from->to 两端点不在一个强连通分量内就给新图加边
{
x=belong[edge[i].from];//对应强连通分量编号
y=belong[edge[i].to];//~
in[y]++;
cb[x].push_back(y);
rdr[y].push_back(x);
}
}
topsort();
for(int i=1;i<=tot;i++) //dp
{
int w=ans[i];//拓扑序
f[w]=dis_[w];
for(int j=0;j<rdr[w].size();j++)
f[w]=max(f[w],f[rdr[w][j]]+dis_[w]);//f[w]=max(f[w],f[v]+dis_[w]) v: v--->w 即 rdr[w][j]
}
for(int i=1;i<=tot;i++) //最后统计答案
sum=max(f[i],sum);
printf("%d\n",sum);
return 0;
}