原創題目 白銀之春 Problem and Solution

白銀之春 Solution

比賽用題面、題解、標程和數據生成器都掛在 git@github.com:sun123zxy/spring.git 上。html

Problem

白銀之春 (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 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...

相關文章
相關標籤/搜索