模板链接:https://www.luogu.org/problemnew/show/P3387
【模板】缩点
题目背景
缩点+DP
题目描述
给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
输入输出格式
输入格式:
第一行,n,m
第二行,n个整数,依次代表点权
第三至m+2行,每行两个整数u,v,表示u->v有一条有向边
输出格式:
共一行,最大的点权之和。
输入输出样例
输入样例#1:
2 2
1 1
1 2
2 1
输出样例#1:
2
说明
n<=10^4,m<=10^5,点权<=1000
算法:Tarjan缩点+DAGdp
题解
解法上面都说了,这里详解一下Tarjan缩点。
样例太水,举个例子:
数组有点多,挨个介绍:
dfn[i]
d
f
n
[
i
]
表示点
i
i
是第几个被dfs到的
表示节点
i
i
能连接到的最上面的点
表示节点
i
i
是否在栈中
:我是栈
f[i]
f
[
i
]
为
i
i
的父节点
染色
val[i]
v
a
l
[
i
]
点权
val2[i]
v
a
l
2
[
i
]
缩点后的权值
直接照着代码讲吧:
void tarjan(int v)
{
dfn[v]=++deep;//标记dfs序
low[v]=deep;//初始的low当然是自己啦
vis[v]=1;//入栈标记
sta[++top]=v;//入栈
int t;
for(int i=x[v].size()-1;i>=0;--i)
{
t=x[v][i];
if(!dfn[t])
{
tarjan(t);//继续dfs
low[v]=min(low[v],low[t]);//更新low
}
else
if(vis[t]) low[v]=min(low[v],low[t]);//如果这个时候有已经标记过的点了,绝壁是自己爸爸,更新同上
}
if(dfn[v]==low[v])
{//如果dfn==low,显然这个点是环的顶端
col[v]=++tot;//染色
vis[v]=0;//弹栈
while(sta[top]!=v)
{//疯狂染色弹栈
col[sta[top]]=tot;
vis[sta[top--]]=0;
}
--top;
}
}
感觉Tarjan很简单呀有木有。。。
手动模拟一下(没有赋值的不显示):
Step 1:普通的dfs+dfn赋值
Step 2:这时,7号点遍历回了6号点,但6已经有了dfn,所以更新low,dfs回溯时,又更新了6号点的low
Step 3:这时,6号点的low==dfn,疯狂弹栈、染色一波
这样我们就求出了第一个环
Step 4:我们再dfs1号点的另一个儿子
Step 5:3号点又连接到了2号点,回溯更新low
Step 6:low[2]==dfn[2],效果同上
Step 7:最后dfs回溯到1号点,它的low没有被更新,low[1]仍等于1,再次退栈,染色。
这样我们就求出了三个环:
(1)6、7
(2)2、3、4、5
(3)1
因为图不一定联通,记得遍历Tarjan。
剩下的就合并一下点的权值,重新建一下图,跑一个记忆化搜索,完结撒花~~~
代码
#include<bits/stdc++.h>
using namespace std;
const int M=1e4+5;
int sta[M],low[M],col[M],dfn[M],val[M],val2[M],f[M];
bool vis[M];
int top,tot,deep,n,m;
vector<int>x[M];
vector<int>dag[M];
void tarjan(int v)
{
dfn[v]=++deep;
low[v]=deep;
vis[v]=1;
sta[++top]=v;
int t;
for(int i=x[v].size()-1;i>=0;--i)
{
t=x[v][i];
if(!dfn[t])
{
tarjan(t);
low[v]=min(low[v],low[t]);
}
else
if(vis[t]) low[v]=min(low[v],low[t]);
}
if(dfn[v]==low[v])
{
col[v]=++tot;
vis[v]=0;
while(sta[top]!=v)
{
col[sta[top]]=tot;
vis[sta[top--]]=0;
}
--top;
}
}
void in()
{
int a,b;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
scanf("%d",&val[i]);
for(int i=1;i<=m;++i)
{
scanf("%d%d",&a,&b);
x[a].push_back(b);
}
for(int i=1;i<=n;++i)
if(!dfn[i]) tarjan(i);
}
void search(int v)
{
if(f[v]) return;
f[v]=val2[v];
int maxn=0;
for(int i=dag[v].size()-1;i>=0;--i)
{
if(!vis[dag[v][i]]) search(dag[v][i]);
maxn=max(maxn,f[dag[v][i]]);
}
f[v]+=maxn;
}
void reb()
{
for(int i=1;i<=n;++i)
{
val2[col[i]]+=val[i];
for(int j=x[i].size()-1;j>=0;--j)
if(col[x[i][j]]!=col[i])
dag[col[i]].push_back(col[x[i][j]]);
}
for(int i=1;i<=tot;++i)
if(!vis[i]) search(i);
}
void ac()
{
int ans=0;
for(int i=1;i<=tot;++i)
ans=max(ans,f[i]);
printf("%d",ans);
}
int main()
{
in();reb();ac();
return 0;
}