缩点+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缩点后变成一张DAG,然后在这张DAG上dp。关键是如何dp?方法有多种,可以先求出DAG的拓扑序,再按照拓扑序从后往前dp,也可以直接标记当前这个儿子是否dp过,若没有dp则dp,dp过了就直接用dp的值进行更新,状态转移方程很容易,即为 dp[ i ]=dp[ i ] + max{ dp[ son[ i ] ] }。
#include
#include
#include
#define N 10010
#define M 100010
using namespace std;
int ver[M],next[M],head[N],dfn[N],low[N],a[N],vc[M],nc[M],hc[N],tc,f[N];
int n,m,tot,num,top,cnt,s[N],ins[N],c[N],bz[N];
void add(int x,int y){ver[++tot]=y,next[tot]=head[x],head[x]=tot;}
void add_c(int x,int y){vc[++tc]=y,nc[tc]=hc[x],hc[x]=tc;}
void tarjan(int x){
dfn[x]=low[x]=++num;ins[x]=1;s[++top]=x;
for(int k=head[x];k;k=next[k]){
int y=ver[k];
if(!dfn[y]){
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(ins[y]) low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x]){
cnt++;int y;
do{
y=s[top--],ins[y]=0;
c[y]=cnt;f[cnt]+=a[y];
}while(x!=y);
}
}
void dp(int x){
int ma=0;bz[x]=1;
for(int i=hc[x];i;i=nc[i]){
int y=vc[i];
if(!bz[y]) dp(y);
ma=max(ma,f[y]);
}
f[x]+=ma;
}
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 x=1;x<=n;x++){
for(int i=head[x];i;i=next[i]){
int y=ver[i];
if(c[x]==c[y]) continue;
add_c(c[x],c[y]);
}
}
for(int i=1;i<=n;i++){
if(!bz[i]){
dp(i);
f[0]=max(f[0],f[i]);
}
}
printf("%d",f[0]);
return 0;
}
作者:zsjzliziyang
QQ:1634151125
转载及修改请注明
本文地址:https://blog.csdn.net/zsjzliziyang/article/details/87023093