洛谷 P3387 【模板】缩点
题目背景
缩点+DP
题目描述
给定一个 nn 个点 mm 条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
输入格式
第一行两个正整数 n,mn,m
第二行 nn 个整数,依次代表点权
第三至 m+2m+2 行,每行两个整数 u,vu,v,表示一条 u ightarrow vu→v 的有向边。
输出格式
共一行,最大的点权之和。
题解:
缩点之后就变成DAG了,就可以拓扑DP了。
代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int maxn=1e4+4;
const int maxm=1e5+5;
int n,m;
int a[maxn];
int tot,head[maxn],nxt[maxm],to[maxm],from[maxm];
int dfn[maxn],low[maxn];
int st[maxn],top,cnt;
bool v[maxn];
int id[maxn],rudu[maxn],dp[maxn];
int tot1,head1[maxn],nxt1[maxm],to1[maxm];
void add(int x,int y)
{
to[++tot]=y;
nxt[tot]=head[x];
from[tot]=x;
head[x]=tot;
}
void add1(int x,int y)
{
to1[++tot1]=y;
nxt1[tot1]=head1[x];
head1[x]=tot1;
}
void tarjan(int x)
{
dfn[x]=low[x]=++cnt;
st[++top]=x;
v[x]=1;
for(int i=head[x];i;i=nxt[i])
{
int y=to[i];
if(!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(v[y])
low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x])
{
int now;
do
{
now=st[top--];
id[now]=x;
v[now]=0;
a[x]+=a[now];
}while(now!=x);
}
}
queue<int> q;
int topsort()
{
for(int i=1;i<=n;i++)
if((id[i]==i)&&(!rudu[i]))
{
q.push(i);
dp[i]=a[i];
}
while(!q.empty())
{
int x=q.front();
q.pop();
for(int i=head1[x];i;i=nxt1[i])
{
int y=to1[i];
dp[y]=max(dp[y],dp[x]+a[y]);
rudu[y]--;
if(!rudu[y])
q.push(y);
}
}
int ans=0;
for(int i=1;i<=n;i++)
ans=max(ans,dp[id[i]]);
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
}
for(int i=1;i<=n;i++)
if(!dfn[i])
tarjan(i);
for(int i=1;i<=m;i++)
{
int x=from[i],y=to[i];
if(id[x]!=id[y])
{
add1(id[x],id[y]);
rudu[id[y]]++;
}
}
printf("%d
",topsort());
return 0;
}