1. 程式人生 > 其它 >洛谷 P2634 [國家集訓隊]聰聰可可

洛谷 P2634 [國家集訓隊]聰聰可可

題幹

聰聰和可可是兄弟倆,他們倆經常為了一些瑣事打起來,例如家中只剩下最後一根冰棍而兩人都想吃、兩個人都想玩兒電腦(可是他們家只有一臺電腦)……遇到這種問題,一般情況下石頭剪刀布就好了,可是他們已經玩兒膩了這種低智商的遊戲。

他們的爸爸快被他們的爭吵煩死了,所以他發明了一個新遊戲:由爸爸在紙上畫n個“點”,並用n1條“邊”把這n個“點”恰好連通(其實這就是一棵樹)。並且每條“邊”上都有一個數。接下來由聰聰和可可分別隨即選一個點(當然他們選點時是看不到這棵樹的),如果兩個點之間所有邊上數的和加起來恰好是3的倍數,則判聰聰贏,否則可可贏。

聰聰非常愛思考問題,在每次遊戲後都會仔細研究這棵樹,希望知道對於這張圖自己的獲勝概率是多少。現請你幫忙求出這個值以驗證聰聰的答案是否正確。

思路

考慮dp[i][j],i為當前的節點,j為0,1,2中的一個數

dp[i][j]表示以i為根節點的子樹中,距離i節點的距離模3為j的個數。

於是我們只需要在遍歷每一個點時對這個陣列進行維護即可。

對於一條邊(u,v)

則滿足條件的個數要加上dp[v][i]*dp[u][mod(-i-邊的長度)] ,i∈{0,1,2} 這裡的(-i-邊的長度)就是為了與i湊一個3出來

mod函式為:

int mod(int x){
    return (x%3+3)%3;
}

就是除以三取餘數

於是就有了以下程式碼:

void add(int u,int v,int len){
    cnt++;
    to[cnt] 
= v; l[cnt] = len; nxt[cnt] = head[u]; head[u] = cnt; } int mod(int x){ return (x%3+3)%3; } int gcd(int x,int y){ if(x%y==0) return y; return gcd(y,x%y); } void dfs(int u,int v){ f[u][0]=1; for(int p=head[u];p;p=nxt[p]){ int vv=to[p]; if(vv==v)continue
; dfs(vv,u); for(int i=0;i<=2;i++) fenzi+=f[vv][i]*f[u][mod(-i-l[p])]*2; for(int i=0;i<=2;i++) f[u][mod(i+l[p])]+=f[vv][i]; } } int main(){ ios::sync_with_stdio(false); cin>>n; for(int i=1;i<n;i++){ int a,b,c; cin>>a>>b>>c; add(a,b,c); add(b,a,c); } dfs(1,-1); fenzi+=n; fenmu=n*n; g=gcd(fenzi,fenmu); fenzi/=g; fenmu/=g; cout<<fenzi<<"/"<<fenmu<<endl;

我們著重再看看這部分:

for(int i=0;i<=2;i++) fenzi+=f[vv][i]*f[u][mod(-i-l[p])]*2;

for(int i=0;i<=2;i++) f[u][mod(i+l[p])]+=f[vv][i];

第一個迴圈是求答案總數,而因為是有序數對所以要乘二。

而我們每次對一顆子樹進行處理的時候,f[u]只更新了在他前面的子樹,所以不會存在有多更新的情況

也就是說不會存在兩條邊在同一子樹下的情況

最後要考慮只有一個點的情況也滿足題意,所以fenzi要+n

AC~