P3387 【模板】缩点
题目背景
缩点+DP
题目描述
给定一个 nn 个点 mm 条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
输入格式
第一行两个正整数 n,m
第二行 n 个整数,依次代表点权
第三至 m+2 行,每行两个整数 u,vu,v,表示一条 u→v 的有向边。
输出格式
共一行,最大的点权之和。
输入输出样例
输入 #1
2 2
1 1
1 2
2 1
输出 #1
2
说明/提示
【数据范围】
对于 100%100% 的数据,1≤n≤104,1<=m<=105点权 ∈[0,1000]
算法:Tarjan 缩点 + DAGdp
视频讲解地址:https://www.bilibili.com/video/av70802550
(声音有点小,这个视频是B站找的,不是本人做的)
题解:就是Tarjan缩点+记忆化搜索
详情请看:https://www.luogu.com.cn/blog/user23342/solution-p3387
(代码参考于上两个链接)
关于Tarjan算法都在上面视频了,这里只给出模板(有详细注释):
注意此算法只能解决有向图
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<stack>
using namespace std;
int d[10005];//每个边的权值
vector<int> e[10005];//图
stack<int> s;//dfs栈
int dfn[10005];//记录dfs次序
int low[10005];//每个顶点能回到的最早时间戳
int pre=0;//时间戳
int color[10005];//记录每个顶点属于的SCC
int x[100005],y[100005];//x[i]->y[i]输入的第i条边
int scc=0;
int sum[10005];//缩点后的权值
int f[10005];//新图每个顶点的如果没访问过就为0,访问过就是从当前点能走到最远的值
void init(int n)
{
for(int i=1;i<=n;i++) e[i].clear() ;
return;
}
void Tarjan(int u)//就是dfs
{
low[u]=dfn[u]=++pre;//记录时间戳,刚遍历到,这两个值当然相同
s.push(u);//入栈
for(int i=0;i<e[u].size() ;i++){
int v=e[u][i];//u->v
if(!dfn[v])
{
Tarjan(v);//继续dfs
low[u]=min(low[u],low[v]);//通过子节点v能回到的最早时间戳
}
else if(!color[v])//如果vdfs访问过且v不是以前dfs的(即color值大于等于1,不在栈s里),而是当前dfs栈中的点
low[u]=min(low[u],dfn[v]);//就能通过v更新当前u能回到的最早时间戳
}
if(low[u]==dfn[u])//判断u是否为某SCC的第一个被发现的顶点 这样才能确定最大的(强连通分量)
{
scc++;//第几个SCC
while(!s.empty() )
{
int x=s.top() ;s.pop() ;//出栈
color[x]=scc;//记录强连通分量
sum[scc]+=d[x];//缩点后要把当前整个SCC的权值合起来!
if(x==u) break;//当前强连通分量记录完毕
}//(染色)
}
return;
}
int dfs(int x)
{
if(f[x]) return f[x];
f[x]=sum[x];//记忆化
int maxx=0;
for(int i=0;i<e[x].size() ;i++)
//if(!f[e[x][i]])//多了这个只有40分,必须要算上所有的出边
maxx=max(maxx,dfs(e[x][i]));
f[x]=f[x]+maxx;//选最长的走
return f[x];
}
int main(void)
{
int n,m,u,v;
//1.输入
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&d[i]);
for(int i=1;i<=m;i++){
scanf("%d %d",&x[i],&y[i]);
e[x[i]].push_back(y[i]); //一条x[i]->y[i]的有向边
}
//2.Tarjan:
pre=0;
for(int i=1;i<=n;i++) if(!dfn[i]) Tarjan(i);//没有访问就dfs
//这图可能是不连通的或者其他不能一次dfs就访问完所有点的图
//-----------以上就是Tarjan算法 -------------------------
//3.通过步骤2 重新建图 : scc->DAG(有向有环图->有向无环图)
init(n);//清空原图
for(int i=1;i<=m;i++)//每一条边
if(color[x[i]]!=color[y[i]])
e[color[x[i]]].push_back(color[y[i]]);//给两个不同的强连通分量连方向同原来的一条边
//-----------以上就是 Tarjan缩点 ------------------------
//4.对生成的新图进行记忆化搜索解答
int ans=0;
for(int i=1;i<=scc;i++){
if(!f[i])//没访问过
{
ans=max(ans,dfs(i));
}
}
printf("%d\n",ans);
return 0;
}