比賽用題面、題解、標程和數據生成器都掛在 git@github.com:sun123zxy/spring.git 上。html
白銀之春 (spring.cpp/.in/.out) (2s,512MB)ios
Backgroundc++
妖夢正在收集春度!git
Descriptiongithub
幻想鄉由 \(n\) 個地點和 \(m\) 條單向小路組成,第 \(i\) 個地點蘊含着 \(s_i\) 的春度。妖夢從位於 \(1\) 號節點的白玉樓出發,沿圖上路徑收集沿路的春度,總春度爲收集到的全部春度之和。算法
半人半靈的妖夢具備一種名叫「人妖槽」的屬性,該屬性有兩種狀態——「人類逢魔」與「妖怪逢魔」,出發時狀態爲「人類逢魔」。某些小路上可能被放置了「森羅結界」。在通過被放置結界的小路時,妖夢的人妖槽狀態將會發生變化——若通過這條小路前人妖槽狀態爲「人類逢魔」,則通過後將變爲「妖怪逢魔」;反之,若通過前狀態爲「妖怪逢魔」,則通過後將變爲「人類逢魔」。當且僅當人妖槽狀態爲「妖怪逢魔」時,妖夢才能夠收集到當前所在地點所蘊含的春度。spring
每一個點的春度只能被收集一次。妖夢能夠在圖上任意遊走,並能夠選擇在任意一個地點中止收集。ide
妖夢但願收集到的總春度最大,但她並無學過OI,請你幫忙算出她最多能收集到多少春度。工具
由於並不是全部人都具備結界內的常識,妖夢也提供了一份題意簡述 :ui
給定一個帶點權普通有向圖和一隻具備 \(0/1\) 狀態的妖夢,從 \(1\) 號節點出發,初始狀態爲 \(0\) 。邊有 \(0/1\) 邊權,通過邊時狀態要異或上邊權。當前狀態爲 \(1\) 時可取得所在點權,點權只能被取得一次。問在圖上隨意遊走可得到的最大點權和。
Input
第一行四個整數 \(n\) , \(m\) ,表示圖由 \(n\) 個點, \(m\) 條邊構成。
接下來一行有 \(n\) 個整數 \(s_i\) ,表示\(i\)號節點蘊含 \(s_i\) 的春度。
接下來 \(m\) 行每行 \(3\) 個整數 \(u_i\) , \(v_i\) , \(w_i\) ,表示有一條從 \(u_i\) 到 \(v_i\) 的有向邊,若 \(w_i = 1\) ,則表示該小路上被放置了森羅結界,若 \(w_i = 0\) ,則表示未被放置。
Output
輸出一行一個整數,表示妖夢能收集到的最大總春度。
Sample 1
Sample 1 Input
5 6 99 82 44 35 3 1 2 1 2 3 0 3 4 1 4 5 0 2 4 1 3 5 1Sample 1 Output
126Sample 1 Explanation
路徑爲 \(1\) -> \(2\) -> \(3\) ,可得到 \(0 \times 99 + 1 \times 82 + 1 \times 44=126\) 點春度。
Sample 2
Sample 2 Input
9 10 9 9 8 2 4 4 3 5 3 1 2 0 2 3 1 3 2 0 3 4 0 4 5 1 5 6 0 6 4 1 2 5 0 7 8 1 9 8 1Sample 2 Output
25Sample 2 Explanation
路徑爲 \(1\) -> \(2\) -> \(3\) -> \(2\) -> \(5\) -> \(6\) ,能夠得到 $0 \times 9 + 0 \times 9 + 1 \times 8 + 1 \times 9 + 1 \times 4 + 1 \times 4= 25 $ 點春度。
Sample 3
見
sample
目錄下spring3.in/.ans
。該樣例是一個無環圖。
Sample 4
見
sample
目錄下spring4.in/.ans
。Constraints
對於30%的數據,保證圖中無環。
對於另外20%的數據,保證圖隨機生成。
對於100%的數據, \(2 \le N \le 5 * 10^5\) , \(1 \le M \le 10^6\) , \(0 \le s_i \le 10^9\) , \(1 \le u_i,v_i \le N\) , \(w_i \in \{ 0,1 \}\) 。
Hints
因爲幻想鄉不受常識束縛,不保證不出現重邊和自環,不保證圖連通。
輸入量較大,請使用較爲快速的讀入方式。
保證時限在std用時的2倍左右。std沒有卡常,請放心食用。
Source
sun123zxy
DAG上dp就行了。設狀態 \(f[u][0/1]\) 爲到達點 \(u\) 時狀態爲 \(0/1\) 可收集到的最大春度,若 \(f[u][t]\) 可達,有
其中 \(\mathrm{val}[u]\) 是點 \(u\) 的權值, \((v,w) \in \mathrm{pre}_u\) 表示 \(u\) 在DAG上的前驅邊, \(\otimes\) 表明異或。
答案即 \(\max_{u \in G} \max(f[u][0],f[u][1])\) 。
普通圖有環,環上的狀態轉移方程相互依賴,沒法dp。
根據部分分的提示,考慮縮點。
不妨先看全部強連通份量都只是簡單環的狀況。
爲了方便描述,咱們定義以下兩種描述:
容易發現奇環上能夠經過繞一圈的方式回到原點,使狀態發生改變。也就是說,不論從進出位置和初始狀態如何,一個奇環總能夠輸出任意的 \(0\) 或 \(1\) 。而若是在奇環上繞兩圈,就能夠取得環上全部點的春度。因此直接縮點處理便可。
那麼偶環如何處理呢?
首先,若進入偶環的的位置(入點)肯定,不管怎樣在偶環上繞圈,到達環上某點(出點)時的狀態老是惟一肯定的。
進一步的,偶環上的點可根據到達該點時的狀態被分爲兩組。組與組之間在環上交錯排列,全部邊權爲 \(1\) 的邊都是都是一個間隔。若入點和出點在同一組內,則狀態不會發生變化;反之則狀態改變。這啓發咱們將偶環縮成兩個點來處理,每個點表明一個組。
考慮春度的獲取。若是進入時狀態爲 \(0\) ,那麼和入點在同一組內的點上的春度都沒法取得(由於通過該點時狀態始終爲 \(0\) ),而在不一樣組的點上的春度可以取得(由於通過該點時狀態始終爲 \(1\) );反之,若進入時狀態爲 \(1\) ,那麼和入點在同一組的點上的春度能夠取得,在不一樣組的不能取得。
縮點後作一些討論就能夠了。
在環上咱們已經發現——奇環能夠特殊處理,而偶環內的點能夠被分紅兩組。強連通份量是否有與其類似的性質呢?
強連通份量無非是許多個環疊起來的連通塊。若是一個強連通份量包含一個或多個奇環(稱之爲「奇強連通份量」),那麼該強連通份量一樣有奇環的性質——每一個點均可以經過在奇環上繞圈得到 \(0/1\) 兩種狀態,塊上全部點的春度都能取得。
實測發現隨機圖中出現偶強連通份量的機率極小,所以只處理奇強連通份量的算法能夠經過隨機圖數據。
剩下的問題已經很明確了——處理所含環全都是偶環的強連通份量(稱之爲「偶強連通份量」)。
能夠發現這一結論:不管如何在偶強連通份量中游走,只要入點和進入時的狀態肯定,那麼每一個點的狀態就惟一肯定。因而偶強連通份量中的點也能夠被分紅兩組,比如環套DAG中的偶環。
易用反證法證實該性質:在一偶強連通份量中,假設點 \(u\) 到點 \(v\) 同時存在偶路徑 \(P\) 和奇路徑 \(Q\) 。那麼奇路徑 \(Q\) 必然與某條從 \(v\) 到 \(u\) 的奇路徑 \(R\) 共同組成了一個偶環(偶強連通份量中只有偶環且各點強連通)。則偶路徑 \(P\) 和奇路徑 \(R\) 構成奇環,與假設矛盾,故性質成立。
春度的獲取也與偶環相同。
判斷一個強連通份量是奇是偶,只需二分圖染色,取環上任意一個點做爲起點DFS,若是能以不一樣的狀態到達某點,那該份量就是奇的,反之則是偶的。正確性比較顯然,證實在此略去。
實現細節較多,建議縮點後從新建圖。
能夠用4個節點分別代理兩個分組各自的入邊和出邊,算出到達該組狀態爲 \(0/1\) 時連通塊內兩個組的點權對答案的貢獻。爲了方便,實現時能夠以邊數x2的代價把節點數壓縮到2個。
/* 白銀之春 (spring) std by sun123zxy PS: If you got a runtime error, "-Wl,--stack=123456789" */ #include<iostream> #include<cstdio> #include<cmath> using namespace std; typedef long long ll; ll Rd(){ ll ans=0;bool fh=0;char c=getchar(); while(c<'0'||c>'9'){if(c=='-') fh=1; c=getchar();} while(c>='0'&&c<='9') ans=ans*10+c-'0',c=getchar(); if(fh) ans=-ans; return ans; } const ll INF=1E18; const ll PTN=1E6+5,EDN=2E6+5; ll N; struct Edge{ll u,v;bool w;ll nxt;}; struct Graph{ Edge edge[EDN]; ll graM,last[PTN]; void GraphInit(){graM=0;for(ll i=0;i<PTN;i++) last[i]=0;} void AddBscEdge(ll u,ll v,bool w){ edge[++graM]=(Edge){u,v,w,last[u]}; last[u]=graM; } void AddUnEdge(ll u,ll v,bool w){ AddBscEdge(v,u,w); } ll ptW[PTN][2]; //value Youmu can get when reaching the vertex with state 0/1 }G1,G2; ll Id(ll cId,bool col){ return 2*cId-col; } ll bel[PTN],cN,rps[PTN]; //belong, number of components, representative vertax of the component ll dfn[PTN],low[PTN],dN; ll stk[PTN],tp;bool isI[PTN]; void Tarjan(ll u){ dfn[u]=low[u]=++dN; stk[++tp]=u;isI[u]=1; for(ll i=G1.last[u];i!=0;i=G1.edge[i].nxt){ ll v=G1.edge[i].v; if(isI[v]){ low[u]=min(low[u],dfn[v]); }else if(!dfn[v]){ Tarjan(v); low[u]=min(low[u],low[v]); } } if(dfn[u]==low[u]){ rps[++cN]=u;ll t; do{ t=stk[tp--]; isI[t]=0;bel[t]=cN; }while(t!=u); } } bool cTyp[PTN]; //component type (0: even; 1: odd) ll col[PTN]; void ColDFS(ll u,bool color,ll curC){ col[u]=color; G2.ptW[Id(curC,color)][1]+=G1.ptW[u][1]; //calculate values for each group (even component) for(ll i=G1.last[u];i!=0;i=G1.edge[i].nxt){ ll v=G1.edge[i].v;bool w=G1.edge[i].w; if(bel[v]!=curC) continue; if(col[v]==-1) ColDFS(v,color^w,curC); else if((color^w)!=col[v]) cTyp[curC]=1; //odd component } } void BuildG2(){ for(ll i=1;i<=G1.graM;i++){ ll u=G1.edge[i].u,v=G1.edge[i].v;bool w=G1.edge[i].w; ll cU=bel[u],cV=bel[v]; if(!cU||!cV) continue; //edges Youmu can never reach if(cU==cV) continue; //edges inside the component ll myV=Id(cV,col[v]*(cTyp[cV]^1)); if(cTyp[cU]==1){ G2.AddUnEdge(Id(cU,0),myV,w); G2.AddUnEdge(Id(cU,0),myV,w^1); }else{ G2.AddUnEdge(Id(cU,col[u]),myV,w); //from this group G2.AddUnEdge(Id(cU,col[u]^1),myV,w^1); //from the other group } } } ll f[PTN][2]; ll F(ll u,bool typ){ if(f[u][typ]!=-1) return f[u][typ]; f[u][typ]=-INF; for(ll i=G2.last[u];i!=0;i=G2.edge[i].nxt){ ll v=G2.edge[i].v;bool w=G2.edge[i].w; f[u][typ]=max(f[u][typ],G2.ptW[u][typ]+F(v,typ^w)); } return f[u][typ]; } ll ST=1; void Solve(){ cN=0;dN=0;tp=0;for(ll i=1;i<=N;i++) dfn[i]=low[i]=0,bel[i]=0,isI[i]=0; Tarjan(ST); //Only need to get components Youmu can reach G2.GraphInit(); for(ll i=1;i<=N;i++) col[i]=-1; for(ll i=1;i<=cN;i++) cTyp[i]=0,ColDFS(rps[i],0,i); for(ll i=1;i<=cN;i++){ if(cTyp[i]==1){ //odd component G2.ptW[Id(i,0)][0]=G2.ptW[Id(i,0)][1]+=G2.ptW[Id(i,1)][1]; //an odd component enjoys all the values G2.ptW[Id(i,1)][0]=G2.ptW[Id(i,1)][1]=0; //abandon Id(i,1) }else{ //even component G2.ptW[Id(i,0)][0]=G2.ptW[Id(i,1)][1]; G2.ptW[Id(i,1)][0]=G2.ptW[Id(i,0)][1]; } } BuildG2(); for(ll i=1;i<=2*N;i++) f[i][0]=f[i][1]=-1; ll myST=Id(bel[ST],col[ST]*(cTyp[bel[ST]]^1)); f[myST][0]=G2.ptW[myST][0]; ll ans=-INF; for(ll i=1;i<=2*N;i++) ans=max(ans,max(F(i,0),F(i,1))); printf("%lld",ans); } int main(){ freopen("spring.in","r",stdin); freopen("spring_std.out","w",stdout); G1.GraphInit(); N=Rd();ll m=Rd(); for(ll u=1;u<=N;u++) G1.ptW[u][1]=Rd(); while(m--){ ll u=Rd(),v=Rd();bool w=Rd(); G1.AddBscEdge(u,v,w); } Solve(); return 0; }
第一次出題,有紕漏請多多包涵。
快要交題時才發現一年前寫的std出鍋了,匆匆忙忙的重寫了一個,不知道有沒有新造出什麼bug。數據也造得比較匆忙,若是爆炸了請隨便辱罵出題人或者去他博客上告訴他(
能夠說這道題把二分圖拓展到了強連通有向圖上,不知道有沒有什麼更有趣的性質能夠發掘。
後來作到幾道性質類似的題目,這裏列出來供參考: 垃圾撞題出題人
思考背景怎樣與題目契合也是個挺有趣的過程。
感謝聽我亂扯idea的 TbYangZ 和 Waper ,以及嘗試叉掉std的兩位勇士 p9t6g 和 changruinian2020 。 雖然都失敗了
就這些吧。
——sun123zxy
Oct. 2019 初稿完成
Nov. 2020 最後更新
Next Phantasm...