CF1402C 「CEOI2020」Star Trek
約定:假設從第\(i\)棵樹的節點\(a\),連向了第\(i+1\)棵樹的節點\(b\),則我們認為第\(i+1\)棵樹是以\(b\)為根的。
考慮一個DP。設\(dp[i]\)表示依次連接了\(i\)棵樹,【從第一棵樹的根節點出發,先手必勝】的方案數。注意,第一棵樹的根節點可以是任意一個節點(只有在求答案時,才強制要求第一棵樹的根節點是\(1\),我們在定義狀態時不考慮這個)。也就是說,\(dp[i]\),是把第一棵樹根節點為\(1\dots n\)的方案數加起來。
轉移時考慮在前面添加一棵樹。
朴素的轉移,我們枚舉添加的這棵樹的根\(\text{root}\)。此時需要做一些分類討論:
- 單獨考慮一棵樹,以\(\text{root}\)為根時,是否是先手必勝的。如果是先手必勝的,此時我們要求連出去的邊不能改變根節點\(\text{root}\)的勝負狀態。具體來說,枚舉樹里每一個節點\(u\),考慮從\(u\)連出去:
- 如果改變\(u\)的勝負性后,不會影響到根節點\(\text{root}\)的勝負性。那么后面\(i-1\)棵樹的連邊方案可以任意。方案數是\(n\times n^{2(i-1)}\)。其中\(n\)表示給后面的第一棵樹定一個根節點,\(n^{2(i-1)}\)表示其他樹之間任意連邊。
- 如果改變\(u\)的勝負性后,會影響到根節點\(\text{root}\)的勝負性:
- 如果\(u\)的子樹是先手必勝的。那么隨便連一條邊,當走到\(u\)時,先手無論如何不會走向這條邊。所以也可以任意連邊,方案數是\(n\times n^{2(i-1)}\)。
- 如果\(u\)的子樹是先手必敗的。則我們連出去的邊必須必勝。因為如果連向了一個必敗狀態,則走到\(u\)時,先手就一定就會走我們新連的這條邊,於是就改變了\(u\)的勝負性,進而改變了根的勝負性。因此,連邊只能連向必勝,方案數是\(dp[i-1]\)。
- 單獨考慮一棵樹,以\(\text{root}\)為根時,如果不是先手必勝(也就是后手必勝),此時我們要求連出去的邊必須改變根節點的勝負狀態。具體來說,枚舉樹里每一個節點\(u\),考慮從\(u\)連出去:
- 如果改變\(u\)的勝負性后,不會影響到根節點\(\text{root}\)的勝負性。那一定不符合我們的要求。所以從\(u\)節點連出去的方案數是\(0\)。
- 如果改變\(u\)的勝負性后,會影響到根節點\(\text{root}\)的勝負性:
- 如果\(u\)的子樹是先手必勝的。那么我們無論怎么連邊,走到\(u\)時先手都不會走我們連出去的邊。所以不可能改變\(u\)的勝負性,進而不可能改變根的勝負性。所以方案數還是\(0\)。
- 如果\(u\)的子樹是先手必敗的。那么我們連向一個必敗點,就能改變\(u\)的勝負性。所以連邊方案數是\(n\times n^{2(i-1)}-dp[i-1]\)。
通過上述的分類討論,我們發現,確定根后,只需要知道每個節點\(u\)的兩個信息:
- 點\(u\)的子樹,是否是先手必勝的。
- 點\(u\)勝負性改變,是否會影響根的勝負性。
具體來說,只需要知道,對每個根來說,兩種信息\(2\times2\)共\(4\)類點的數量分別是多少,就能計算出DP的轉移系數。然后用矩陣快速冪優化DP。
首先,可以枚舉根,各做一次樹形DP,求出這兩個信息。時間復雜度\(O(n^2)\)。具體來說:
- 點\(u\)的子樹是否先手必勝。這個只需要看兒子里,有沒有先手必敗的點即可。只要存在先手必敗的兒子,則點\(u\)就是先手必勝的,否則是先手必敗的。
- 點\(u\)的勝負性改變,能否影響到根。這個要滿足兩個條件:
- 首先,點\(u\)的父親,必須能影響根。
- 其次,點\(u\)的所有兄弟里(它父親除了\(u\)以外的兒子),必須沒有必敗點。也就是說,要么點\(u\)父親的兒子里壓根沒有必敗點;要么恰好有一個必敗點且這個點就是\(u\)。
這兩個DP都比較簡單。假設求出的兩個信息,分別為\(f_{\text{root}}(u),g_{\text{root}}(u)\)(\(f_{\text{root}}(u),g_{\text{root}}(u)\in\{0,1\}\))。那相當於對每個\(\text{root}\),求出一個:
假設有了這個\(\text{cnt}\)數組,我們就很容易求出系數,然后進行上述的DP並用矩陣快速冪優化了。
先對\(1\),求出\(\text{cnt}[1][x][y]\),然后換根。換根的時候非常復雜,需要進行大量的討論。具體來說,要預處理出一個\(s(u)\)表示只考慮(以\(1\)為根時)點\(u\)的子樹,子樹里有多少\(g_{1}(i)=1\)的點。換句話說,就是不考慮\(u\)的父親了,強制點\(g(u)=1\)。這個\(s\)在換根時,有很大的用處。
其他細節,留給讀者自行討論。實在討論不出來可以看看參考代碼。不過看到這么長的代碼,你估計寧願自己討論......。
時間復雜度\(O(n+\log d)\)。
參考代碼:
//problem:C
//CEOI2020 D1T3
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
template<typename T>inline void ckmax(T& x,T y){x=(y>x?y:x);}
template<typename T>inline void ckmin(T& x,T y){x=(y<x?y:x);}
const int MAXN=1e5;
const int MOD=1e9+7;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int& x,int y){x=mod1(x+y);}
inline void sub(int& x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}
int n;
ll D;
vector<int>G[MAXN+5];
int root_f[MAXN+5],subt_f[MAXN+5],subt_cnt_0[MAXN+5],root_cnt_0[MAXN+5];
void dfs1(int u,int fa){
subt_f[u]=0; // 先手必敗
subt_cnt_0[u]=0;
for(int i=0;i<SZ(G[u]);++i){
int v=G[u][i];
if(v==fa) continue;
dfs1(v,u);
if(subt_f[v]==0){
subt_f[u]=1;
subt_cnt_0[u]++;
}
}
}
int root_aff[MAXN+5][2],subt_aff[MAXN+5][2];
int sum[MAXN+5][2],sum_0[MAXN+5][2];
void dfs2(int u,int fa){
root_aff[u][0]=root_aff[u][1]=0;
root_aff[u][subt_f[u]]=1;
for(int i=0;i<SZ(G[u]);++i){
int v=G[u][i];
if(v==fa) continue;
dfs2(v,u);
if(subt_cnt_0[u] - (subt_f[v]==0) == 0){
root_aff[u][0]+=root_aff[v][0];
root_aff[u][1]+=root_aff[v][1];
}
sum[u][0]+=subt_aff[v][0];
sum[u][1]+=subt_aff[v][1];
if(subt_f[v]==0){
sum_0[u][0]+=subt_aff[v][0];
sum_0[u][1]+=subt_aff[v][1];
}
}
subt_aff[u][0]=root_aff[u][0];
subt_aff[u][1]=root_aff[u][1];
}
int aff[MAXN+5];// 每個點的subt_f值改變,對根的f值是否有影響
void dfs3(int u,int fa){
if(!aff[u]) return;
if(subt_cnt_0[u]>1) return;
for(int i=0;i<SZ(G[u]);++i){
int v=G[u][i];
if(v==fa) continue;
if(subt_cnt_0[u] - (subt_f[v]==0) == 0){
aff[v]=1; // 除v以外都是0
}
else{
aff[v]=0;
continue;
}
dfs3(v,u);
}
}
int cnt[MAXN+5][2][2];
int tmp[2],tmp_subt_cnt_aff_1[2];
void dfs_changeRoot(int u,int fa,bool aff_clear){
if(root_cnt_0[fa] - (subt_f[u]==0) == 0){
aff[u]=1;
}
if(aff_clear){
aff[u]=0;
}
//cerr<<"---------------- "<<u<<" --------------------"<<endl;
int ff=0;
if(fa){
// 求以u為根時的勝負情況 root_f[u]
ff=root_f[fa];
if(root_cnt_0[fa]==1 && subt_f[u]==0){
assert(ff==1);
ff=0;
}
root_cnt_0[u]=subt_cnt_0[u]+(ff==0);
root_f[u]=(root_cnt_0[u]>0);
// 以u為根時,重新求root_aff
sum[fa][0]-=subt_aff[u][0];
sum[fa][1]-=subt_aff[u][1];
if(subt_f[u]==0){
sum_0[fa][0]-=subt_aff[u][0];
sum_0[fa][1]-=subt_aff[u][1];
}
tmp[0]=root_aff[fa][0];
tmp[1]=root_aff[fa][1];
if(root_cnt_0[fa] - (subt_f[u]==0) == 0){
tmp[0]-=root_aff[u][0];
tmp[1]-=root_aff[u][1];
}
tmp[root_f[fa]]--;
tmp[ff]++;
if(subt_f[u]==0){
if(root_cnt_0[fa]==1){
tmp[0]+=sum[fa][0];
tmp[1]+=sum[fa][1];
}
if(root_cnt_0[fa]==2){
tmp[0]+=sum_0[fa][0];
tmp[1]+=sum_0[fa][1];
}
}
root_aff[u][0]=root_aff[u][1]=0;
root_aff[u][root_f[u]]=1;
if(root_cnt_0[u] - (ff==0) == 0){
root_aff[u][0]+=tmp[0];
root_aff[u][1]+=tmp[1];
}
for(int i=0;i<SZ(G[u]);++i){
int v=G[u][i];
if(v==fa) continue;
if(root_cnt_0[u] - (subt_f[v]==0) == 0){
root_aff[u][0]+=root_aff[v][0];
root_aff[u][1]+=root_aff[v][1];
}
}
// 求以u為根時,整棵樹里aff和f為0/1的數量 cnt[u][0/1][0/1]
// 先繼承fa的
for(int i=0;i<=1;++i)for(int j=0;j<=1;++j)cnt[u][i][j]=cnt[fa][i][j];
// 分三個部分
// 1. u節點本身
// 2. u原本的子樹
// 3. fa變成u的兒子后新產生的子樹
// part1
cnt[u][aff[u]][subt_f[u]]--;
cnt[u][1][root_f[u]]++;
// part2
int oaff=aff[u];
if(!aff[u]){
if(ff){
// fa變成兒子后,對u的子樹無影響
// 但aff[u]變成了1后,子樹會產生連鎖反應
aff_clear=0;
aff[u]=1;
subt_aff[u][subt_f[u]]--;
cnt[u][0][0]-=subt_aff[u][0];
cnt[u][1][0]+=subt_aff[u][0];
cnt[u][0][1]-=subt_aff[u][1];
cnt[u][1][1]+=subt_aff[u][1];
subt_aff[u][subt_f[u]]++;
}
else{
// 受到fa的影響, u的子樹里(不含u)仍然沒有aff=1的點, 所以cnt不變
aff[u]=1;
}
}
else{
if(!ff){
// 受到fa的影響, u子樹里一些原本aff=1的點(不含u), 現在全部變成aff=0
aff_clear=1;
subt_aff[u][subt_f[u]]--;
cnt[u][1][0]-=subt_aff[u][0];
cnt[u][0][0]+=subt_aff[u][0];
cnt[u][1][1]-=subt_aff[u][1];
cnt[u][0][1]+=subt_aff[u][1];
subt_aff[u][subt_f[u]]++;
}
}
// part3
assert(aff[fa]==1);
if(subt_cnt_0[u]==0){
// fa變成兒子后,aff還是1
cnt[u][1][root_f[fa]]--;
cnt[u][1][ff]++;
if(subt_f[u]==0){
// fa的其它兒子(u原來的兄弟,aff從0變成1)
if(root_cnt_0[fa]==1){
cnt[u][0][0]-=sum[fa][0];
cnt[u][1][0]+=sum[fa][0];
cnt[u][0][1]-=sum[fa][1];
cnt[u][1][1]+=sum[fa][1];
}
if(root_cnt_0[fa]==2){
cnt[u][0][0]-=sum_0[fa][0];
cnt[u][1][0]+=sum_0[fa][0];
cnt[u][0][1]-=sum_0[fa][1];
cnt[u][1][1]+=sum_0[fa][1];
}
}
}
else{
// fa變成兒子后,aff變成0了
cnt[u][1][root_f[fa]]--;
cnt[u][0][ff]++;
cnt[fa][1][root_f[fa]]--;
cnt[fa][1][0]-=oaff*subt_aff[u][0];
cnt[fa][1][1]-=oaff*subt_aff[u][1];
cnt[u][1][0]-=cnt[fa][1][0];
cnt[u][0][0]+=cnt[fa][1][0];
cnt[u][1][1]-=cnt[fa][1][1];
cnt[u][0][1]+=cnt[fa][1][1];
cnt[fa][1][0]+=oaff*subt_aff[u][0];
cnt[fa][1][1]+=oaff*subt_aff[u][1];
cnt[fa][1][root_f[fa]]++;
}
}
//for(int i=0;i<=1;++i)for(int j=0;j<=1;++j)cerr<<"aff "<<i<<" f "<<j<<": "<<cnt[u][i][j]<<endl;
int x=subt_aff[fa][0],y=subt_aff[fa][1],z=subt_f[fa];
if(fa){
subt_aff[fa][0]=tmp[0],subt_aff[fa][1]=tmp[1],subt_f[fa]=ff;
sum[u][0]+=subt_aff[fa][0];
sum[u][1]+=subt_aff[fa][1];
if(ff==0){
sum_0[u][0]+=subt_aff[fa][0];
sum_0[u][1]+=subt_aff[fa][1];
}
}
for(int i=0;i<SZ(G[u]);++i){
int v=G[u][i];
if(v==fa) continue;
dfs_changeRoot(v,u,aff_clear);
}
if(fa){
subt_aff[fa][0]=x,subt_aff[fa][1]=y,subt_f[fa]=z;
sum[fa][0]+=subt_aff[u][0];
sum[fa][1]+=subt_aff[u][1];
if(subt_f[u]==0){
sum_0[fa][0]+=subt_aff[u][0];
sum_0[fa][1]+=subt_aff[u][1];
}
}
}
struct Matrix{
int a[2][2];
void identity(){a[0][0]=a[1][1]=1;a[0][1]=a[1][0]=0;}
Matrix(){a[0][0]=a[0][1]=a[1][0]=a[1][1]=0;}
};
Matrix operator*(const Matrix& X,const Matrix& Y){
Matrix Z;
for(int i=0;i<=1;++i)for(int j=0;j<=1;++j)for(int k=0;k<=1;++k)Z.a[i][j]=((ll)Z.a[i][j] + (ll)X.a[i][k]*Y.a[k][j])%MOD;
return Z;
}
Matrix mat_pow(Matrix X,ll i){
Matrix Y;Y.identity();
while(i) { if(i&1LL) Y=Y*X; X=X*X; i>>=1; }
return Y;
}
int main() {
cin>>n>>D;
for(int i=1;i<n;++i){
int u,v;
cin>>u>>v;
G[u].pb(v);
G[v].pb(u);
}
dfs1(1,0);
dfs2(1,0);
aff[1]=1;
dfs3(1,0);
//for(int i=1;i<=n;++i)cerr<<aff[i]<<" ";cerr<<endl;
for(int i=1;i<=n;++i){
cnt[1][aff[i]][subt_f[i]]++;
}
root_f[1]=subt_f[1];
root_cnt_0[1]=subt_cnt_0[1];
dfs_changeRoot(1,0,0);
int trans_coef_f=0,trans_coef_n=0;
int ans_coef_f=0,ans_coef_n=0;
for(int i=1;i<=n;++i){
if(root_f[i]){
add(trans_coef_n,cnt[i][0][0]+cnt[i][0][1]+cnt[i][1][1]);
add(trans_coef_f,cnt[i][1][0]);
}
else{
add(trans_coef_n,cnt[i][1][0]);
sub(trans_coef_f,cnt[i][1][0]);
}
if(i==1){
ans_coef_f=trans_coef_f;
ans_coef_n=trans_coef_n;
}
}
/*
static int dp[MAXN+5];
dp[1]=0;
for(int i=1;i<=n;++i)if(root_f[i]==1)dp[1]++;
int v=n;
for(int i=2;i<=D;++i){
dp[i]=((ll)dp[i-1]*trans_coef_f%MOD + (ll)v*trans_coef_n%MOD)%MOD;
v=(ll)v*n%MOD*n%MOD;
}
int ans=((ll)dp[D]*ans_coef_f%MOD + (ll)v*ans_coef_n%MOD)%MOD;
cout<<ans<<endl;
*/
Matrix res;
res.a[0][0]=0;
for(int i=1;i<=n;++i)if(root_f[i]==1)res.a[0][0]++;
res.a[0][1]=n;
Matrix trans;
trans.a[0][0]=trans_coef_f;
trans.a[1][0]=trans_coef_n;
trans.a[1][1]=(ll)n*n%MOD;
res=res*mat_pow(trans,D-1);
int ans=((ll)res.a[0][0]*ans_coef_f%MOD + (ll)res.a[0][1]*ans_coef_n%MOD)%MOD;
cout<<ans<<endl;
return 0;
}