题目背景
缩点+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
先缩点,把在同一个强连通分量里的点合并起来。然后重新建图,每个点跑一下最长路就行了。就当练习模板了。
注意SPFA的时候用的是点权,不是边权。重新建图的时候要把原来的图重置一下。
#include<bits/stdc++.h>
using namespace std;
const int maxn=100010;
int Head[maxn],Next[maxn],V[maxn],W[maxn],val[maxn],x[maxn],y[maxn];
int st[maxn],ins[maxn],dfn[maxn],low[maxn],belong[maxn],dis[maxn],vis[maxn];
int cnt=0,tot=0,sz=0,top=0,ans=0,n,m,u,v;
void add(int u,int v){
++cnt;
Next[cnt]=Head[u];
V[cnt]=v;
Head[u]=cnt;
}
void tarjan(int u){
st[++top]=u,ins[u]=1;
dfn[u]=low[u]=++sz;
for(int i=Head[u];i;i=Next[i]){
int v=V[i];
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(ins[v]) low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
int j;tot+=1;
while(j!=u){
j=st[top--];
ins[j]=0;
belong[j]=tot;
W[tot]+=val[j];
}
}
}
void SPFA(int s){
queue<int> Q;
memset(dis,0,sizeof(dis));
memset(vis,0,sizeof(vis));
dis[s]=W[s];
Q.push(s);
while(!Q.empty()){
int u=Q.front();
Q.pop();
vis[u]=0;
for(int i=Head[u];i!=-1;i=Next[i]){
int v=V[i];
if(dis[v]<dis[u]+W[v]){
dis[v]=dis[u]+W[v];
if(!vis[v]){
vis[v]=1;
Q.push(v);
}
}
}
}
for(int i=1;i<=tot;++i)
ans=max(ans,dis[i]);
}
int main(){
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",&u,&v);
add(u,v);
x[i]=u,y[i]=v;
}
for(int i=1;i<=n;++i){
if(!dfn[i])
tarjan(i);
}
memset(Head,-1,sizeof(Head));
memset(Next,-1,sizeof(Next));
memset(V,0,sizeof(V));
cnt=0;
for(int i=1;i<=m;++i)
if(belong[x[i]]!=belong[y[i]])
add(belong[x[i]],belong[y[i]]);
for(int i=1;i<=tot;++i) SPFA(i);
printf("%d\n",ans);
}