【tarjan縮點+最長路】luogu P3387
阿新 • • 發佈:2018-11-10
題目背景
縮點+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); }