1. 程式人生 > 實用技巧 >原創題目 白銀之春 Problem and Solution

原創題目 白銀之春 Problem and Solution

白銀之春 Solution

比賽用題面、題解、標程和資料生成器都掛在 [email protected]:sun123zxy/spring.git 上。

Problem

白銀之春 (spring.cpp/.in/.out) (2s,512MB)

Background

妖夢正在收集春度!

Description

幻想鄉由 \(n\) 個地點和 \(m\) 條單向小路組成,第 \(i\) 個地點蘊含著 \(s_i\) 的春度。妖夢從位於 \(1\) 號節點的白玉樓出發,沿圖上路徑收集沿路的春度,總春度為收集到的所有春度之和。

半人半靈的妖夢具有一種名叫“人妖槽”的屬性,該屬性有兩種狀態——“人類逢魔”與“妖怪逢魔”,出發時狀態為“人類逢魔”。某些小路上可能被放置了“森羅結界”。在經過被放置結界的小路時,妖夢的人妖槽狀態將會發生變化——若經過這條小路前人妖槽狀態為“人類逢魔”,則經過後將變為“妖怪逢魔”;反之,若經過前狀態為“妖怪逢魔”,則經過後將變為“人類逢魔”。當且僅當人妖槽狀態為“妖怪逢魔”時,妖夢才可以收集到當前所在地點所蘊含的春度。

每個點的春度只能被收集一次。妖夢可以在圖上任意遊走,並可以選擇在任意一個地點停止收集。

妖夢希望收集到的總春度最大,但她並沒有學過OI,請你幫忙算出她最多能收集到多少春度。

因為並非所有人都具有結界內的常識,妖夢也提供了一份題意簡述 :

給定一個帶點權普通有向圖和一隻具有 \(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 1

Sample 1 Output

126

Sample 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 1

Sample 2 Output

25

Sample 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

Fun Facts

Solution

無環圖

DAG上dp就好了。設狀態 \(f[u][0/1]\) 為到達點 \(u\) 時狀態為 \(0/1\) 可收集到的最大春度,若 \(f[u][t]\) 可達,有

\[f[u][t] = t \times \mathrm{val}[u] + \max_{(v,w) \in \mathrm{pre}_u} f[v][t \otimes w] \]

其中 \(\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。

根據部分分的提示,考慮縮點。

不妨先看所有強連通分量都只是簡單環的情況。

環套DAG

為了方便描述,我們定義如下兩種描述:

  • 奇環:環上所有邊權異或和為 \(1\) 的環。
  • 偶環:環上所有邊權異或和為 \(0\) 的環。

容易發現奇環上可以通過繞一圈的方式回到原點,使狀態發生改變。也就是說,不論從進出位置和初始狀態如何,一個奇環總可以輸出任意的 \(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個。

Code

/*
白銀之春 (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;
}

Omake

第一次出題,有紕漏請多多包涵。

快要交題時才發現一年前寫的std出鍋了,匆匆忙忙的重寫了一個,不知道有沒有新造出什麼bug。資料也造得比較匆忙,如果爆炸了請隨便辱罵出題人或者去他部落格上告訴他(

可以說這道題把二分圖拓展到了強連通有向圖上,不知道有沒有什麼更有趣的性質可以發掘。

後來做到幾道性質相似的題目,這裡列出來供參考: 垃圾撞題出題人

思考背景怎樣與題目契合也是個挺有趣的過程。

感謝聽我亂扯idea的 TbYangZ 和 Waper ,以及嘗試叉掉std的兩位勇士 p9t6g 和 changruinian2020 。 雖然都失敗了

就這些吧。

——sun123zxy

Oct. 2019 初稿完成

Nov. 2020 最後更新

Next Phantasm...