正题
题目链接:https://www.luogu.com.cn/problem/P6803
题目大意
给出一棵nnn个点的树,把它复制出D+1D+1D+1层,编号为[0,D][0,D][0,D],然后每一层随机一个点向下一层随机一个点连边。
然后从第000层的111号点出发,两个人轮流操作走向一个之前没有走过的点,求有多少种连边方案使得先手必胜。
1≤n≤105,1≤D≤10181\leq n\leq 10^5,1\leq D\leq 10^{18}1≤n≤105,1≤D≤1018
解题思路
我们先只考虑连到下一层的那个点是必胜还是必败的。
显然,连接的下一层的点如果是必胜的,那么局面不会有任何改变。而如果连接的是必败的点,那么原本必败的情况就会变成必胜的情况。
考虑对于每个点算出Gx,0/1G_{x,0/1}Gx,0/1表示从xxx出发的情况,如果连接的下层点必败,这一层有多少种连边情况会先手必胜/先手必败。
这个东西虽然比较麻烦,但是可以通过换根dpdpdp求出。
然后把所有点的求个和得到S0,0/1S_{0,0/1}S0,0/1表示下一层连接先手必败的点,这一层得到胜/负的方案。
同样的通过每一个点为根时树的胜负情况得到S1,0/1S_{1,0/1}S1,0/1表示下一层连接先手必胜的点(局面不会改变),这一层得到胜/负的方案。
这样做DDD层我们就可以直接矩阵乘法了。
时间复杂度:O(n+logD)O(n+\log D)O(n+logD)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define ll long long
using namespace std;
const ll N=1e5+10,S=2,P=1e9+7;
struct Matrix{ll a[S][S];
}s,ans,c;
Matrix operator*(const Matrix &a,const Matrix &b){memset(c.a,0,sizeof(c.a));for(ll i=0;i<S;i++)for(ll j=0;j<S;j++)for(ll k=0;k<S;k++)(c.a[i][j]+=a.a[i][k]*b.a[k][j]%P)%=P;return c;
}
struct node{ll to,next;
}a[N<<1];
ll n,d,tot,ls[N],g[N][2];
bool f[N];vector<ll> q[N];
void addl(ll x,ll y){a[++tot].to=y;a[tot].next=ls[x];ls[x]=tot;return;
}
void dfs(ll x,ll fa){ll p=0;for(ll i=ls[x];i;i=a[i].next){ll y=a[i].to;if(y==fa)continue;dfs(y,x);f[x]|=!f[y];if(!f[y])p=(p?-1:y);g[x][1]+=g[y][0];g[x][0]+=g[y][1];}if(p==-1)g[x][1]+=g[x][0];else if(p>0)g[x][1]+=g[x][0]-g[p][1];if(p==-1)g[x][0]=0;else if(p>0)g[x][0]=g[p][1];g[x][1]++;return;
}
void solve(ll x,ll fa,ll p,ll s0,ll s1){if(!p)q[x].push_back(fa);for(ll i=ls[x];i;i=a[i].next){ll y=a[i].to;if(y==fa)continue;if(!f[y]&&q[x].size()<3)q[x].push_back(y);s0+=g[y][1];s1+=g[y][0];}if(!q[x].size())p=0;else if(q[x].size()==1)p=q[x][0];else p=-1;int pg0=g[x][0],pg1=g[x][1];g[x][0]=s0,g[x][1]=s1;if(p==-1)g[x][1]+=g[x][0],g[x][0]=0;else if(p>0)g[x][1]+=g[x][0]-g[p][1],g[x][0]=g[p][1];g[x][1]++;ll _f=(p?1:0);ans.a[0][_f]++;s.a[0][0]+=g[x][0];s.a[0][1]+=g[x][1];s.a[1][0]+=(!_f)*n;s.a[1][1]+=_f*n;for(ll i=ls[x];i;i=a[i].next){ll y=a[i].to;if(y==fa)continue;if(!q[x].size())p=0;else if(q[x].size()==1){if(q[x][0]==y)p=0;else p=q[x][0];}else if(q[x].size()==2){if(q[x][0]==y)p=q[x][1];else if(q[x][1]==y)p=q[x][0];else p=-1;}else p=-1;g[x][0]=s0-g[y][1];g[x][1]=s1-g[y][0];if(p==-1)g[x][1]+=g[x][0],g[x][0]=0;else if(p>0)g[x][1]+=g[x][0]-g[p][1],g[x][0]=g[p][1];g[x][1]++;solve(y,x,p?1:0,g[x][1],g[x][0]);}g[x][0]=pg0;g[x][1]=pg1;return;
}
signed main()
{scanf("%lld%lld",&n,&d);for(ll i=1,x,y;i<n;i++){scanf("%lld%lld",&x,&y);
// x=i+1;y=(i+1)/2;addl(x,y);addl(y,x);}dfs(1,0);ll A=g[1][1];solve(1,0,1,0,0);s.a[0][0]%=P;s.a[0][1]%=P;s.a[1][0]%=P;s.a[1][1]%=P;d--;while(d){if(d&1)ans=ans*s;s=s*s;d>>=1;}ll answer=A*ans.a[0][0]%P;(answer+=f[1]*n*ans.a[0][1]%P)%=P;printf("%lld\n",answer);return 0;
}