洛谷 P2634 [國家集訓隊]聰聰可可
題幹
聰聰和可可是兄弟倆,他們倆經常為了一些瑣事打起來,例如家中只剩下最後一根冰棍而兩人都想吃、兩個人都想玩兒電腦(可是他們家只有一臺電腦)……遇到這種問題,一般情況下石頭剪刀布就好了,可是他們已經玩兒膩了這種低智商的遊戲。
他們的爸爸快被他們的爭吵煩死了,所以他發明了一個新遊戲:由爸爸在紙上畫n個“點”,並用n−1條“邊”把這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~