2021牛客暑期多校训练营4 部分题题解
B.Sample Game
题目链接
简要题解
我们发现,只要确定了每一个数出现了多少次,就可以唯一确定当前的一个合法序列,也就是递增序列。
我们不知道这个合法序列的最终长度,但是这个最终长度肯定大于当前长度。
因此我们可以设\(F[i]\)表示最终长度大于\(i\)的概率,那么很容易知道我们所要求的就是$$Ans=\sum_{i=0}^{\infty} (i+1)^2(F[i]-F[i+1])$$
整理一下,可以得到$$Ans=\sum_{i=0}^{\infty} i^2 F[i]-\sum_{i=0}^{\infty} (i+1)^2 F[i+1]+2\sum_{i=0}^{\infty}
iF[i]+\sum_{i=0}^{\infty} F[i]$$
我们需要对这个东西敏感,因为我们可以构造函数来求得\(\sum_{i=0}^{\infty}
iF[i]\) 和\(\sum_{i=0}^{\infty} F[i]\)之间的关系。
单独的\(F[i]\)很难求,但是\(\sum_{i=0}^{\infty} F[i]\)是可以求出来的。
我们利用生成函数,令\(f(x)=\sum_{i=0}^{\infty} F[i]*x^i\)
考虑\(F[i]\)的意义,我们有\(f(x)=\prod_{i=1}^n\sum_{j=0}^{\infty}P_i^j\),即枚举第\(i\)个数出现了\(j\)次
等比数列求和得到$$f(x)=\prod_{i=1}^n \frac{1}{1-P_i*x}$$
那么$$f(1)=\sum_{i=0}^{\infty} F[i]=\prod_{i=1}^n \frac{1}{1-P_i}$$
我们现在还需要求\(\sum_{i=0}^{\infty} iF[i]\),事实上只要对\(f(x)\)求导就求出来了
对\(f(x)\)求\(ln\)可以得到$$ln(f(x))=-\sum_{i=1}^{\infty}ln(1-P_i*x)$$
两边求导得到$$ \frac{f'(x)} {f(x)}=\sum_{i=1}^{\infty} \frac{P_i} {1-P_i*x} $$
并且\(f'(x)=\sum_{i=0}^{\infty} iF[i]*x^{i-1}\),我们只需要求\(f'(1)\)即可。
代码如下
#include<bits/stdc++.h>
using namespace std;
const int MAXN=110;
const int Mod=998244353;
int n,Sw,W[MAXN],P[MAXN],G[MAXN];
int F=1,F1;
int Pow(int Down,int Up)
{ int Ret=1,Now=Down;
for(;Up>=1;Up>>=1) Up&1?Ret=1ll*Ret*Now%Mod:0,Now=1ll*Now*Now%Mod;
return Ret;
}
int main()
{ scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&W[i]),Sw=(Sw+W[i])%Mod;
for(int i=1;i<=n;i++) P[i]=1ll*W[i]*Pow(Sw,Mod-2)%Mod;
for(int i=1,D;i<=n;i++)
D=Pow((Mod+1-P[i])%Mod,Mod-2),F=1ll*F*D%Mod,F1=(F1+1ll*P[i]*D)%Mod;
printf("%lld\n",(2ll*F*F1+F)%Mod);
}
E.Tree Xor
题目链接
简要题解
我们可以发现,只要确定了树上任一点的权值,所有节点的权值就都确定了。
我们\(Dfs\)预处理出\(Val[i]\),也就是\(i\)到根节点路径上边权的异或和,那么\(W[i]=W[1]\bigoplus Val[i]\)
每一个节点有一个限制权值的区间,我们希望通过这个区间和\(Val\)来得到关于\(W[1]\)的所有限制。
一个区间内的所有数异或上某个数形成的新集合,并不一定是一个区间,这使得维护起来很麻烦。
但是有一类区间很特殊,这个区间内的所有数异或上同一个数后还是一个区间。
如果有一个长度为\(2^k\)的区间,这个区间内的所有数右移\(k\)位后全部相等,那么这个区间就符合上述性质。
由于区间内有\(2^k\)个数,那么这些数二进制下的低\(k\)位形成的集合就是\(k\)位的全集,这个集合异或上任何数还是这个集合。
并且高位全部相等,所以异或之后还是一个连续的区间。
我们可以把每个点的限制区间写成\(logn\)个具有这样性质的区间,与该点的\(Val\)值异或得到\(W[1]\)的限制区间。
把每个点的区间求交,就可以得到最终合法的\(W[1]\)值有多少
这里采用了动态开点线段树来维护区间的交
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+10;
struct SEC{ int Le,Ri; }Sec[MAXN];
struct EDGE{ int u,v,w,Next; }Edge[MAXN*2];
struct DOT{ int Lson,Rson,Sum; }Tr[MAXN*100];
int n,Es,Root,Ts;
int First[MAXN],Val[MAXN],Two[31];
int Read()
{ int a=0,c=1; char b=getchar();
while(b!='-'&&(b<'0'||b>'9')) b=getchar();
if(b=='-') c=-1,b=getchar();
while(b>='0'&&b<='9') a=a*10+b-48,b=getchar();
return a*c;
}
int Max(int A,int B){ return A>B?A:B; }
void Link(int u,int v,int w){ Edge[++Es]=(EDGE){u,v,w,First[u]},First[u]=Es; }
void Push_up(int S){ Tr[S].Sum=Tr[Tr[S].Lson].Sum+Tr[Tr[S].Rson].Sum; }
void Modify(int &S,int Le,int Ri,int Al,int Ar)
{ if(!S) S=++Ts;
if(Tr[S].Sum==Ri-Le+1) return ;
if(Al<=Le&&Ri<=Ar) return Tr[S].Sum=Ri-Le+1,(void)0;
int Mid=(Le+Ri)>>1;
if(Al<=Mid) Modify(Tr[S].Lson,Le,Mid,Al,Ar);
if(Mid<Ar) Modify(Tr[S].Rson,Mid+1,Ri,Al,Ar);
Push_up(S);
}
void Pre_Modify(int Le,int Nb,int Xor){ Le=(Le^Xor)>>Nb<<Nb,Modify(Root,0,Two[30]-1,Le,Le+Two[Nb]-1); }
void Dfs(int Now,int Fa)
{ for(int i=First[Now],v;i!=-1;i=Edge[i].Next)
if((v=Edge[i].v)!=Fa) Val[v]=Val[Now]^Edge[i].w,Dfs(v,Now);
}
void Die(int Nl,int Nr,int Nv)
{ if(Nl>Nr) return ;
for(int j=0;Nl+Two[j]-1<=Nr;j++)
if(Nl>>j&1) Pre_Modify(Nl,j,Nv),Nl+=Two[j];
for(int j=30;j>=0;j--)
if(Nl+Two[j]-1<=Nr) Pre_Modify(Nl,j,Nv),Nl+=Two[j];
}
int main()
{ n=Read(),memset(First,-1,sizeof(First));
for(int i=0;i<=30;i++) Two[i]=1<<i;
for(int i=1;i<=n;i++) Sec[i].Le=Read(),Sec[i].Ri=Read();
for(int i=1,u,v,w;i<n;i++) u=Read(),v=Read(),w=Read(),Link(u,v,w),Link(v,u,w);
Dfs(1,1);
for(int i=1,Nl,Nr;i<=n;i++)
Nl=Sec[i].Le,Nr=Sec[i].Ri,Die(0,Nl-1,Val[i]),Die(Nr+1,Two[30]-1,Val[i]);
printf("%d\n",Two[30]-Tr[1].Sum);
}
G.Product
题目链接
简要题解
我们先用数学语言将答案表示出来。
\((a_i+k)!\)不利于推导公式,我们换一种写法
如果\(a_i\)没有限制,那么\(D!\sum_{a_i\geq 0,\sum a_i=D}\prod_{i=1}^n\frac{1}{a_i!}\)的值可以用组合意义推导。
假设我们有\(D\)个有区别的小球,要放入\(n\)个有区别的盒子中。
我们可以这样放球来保证方案不重不漏:先给小球编号,然后确定一个拿球的顺序,接着按顺序把球放入盒子内。由于我们确定了拿球的顺序,那么放球的顺序就必须固定,比如说从前往后放球。我们发现,方案还是算多了,因为最终在同一个盒子里的那些球,无论放入的顺序是什么,都只对应一种方案。
假设最终某个盒子里有\(a_i\)个球,由于我们确定了拿球顺序和放球顺序,那么这个盒子里有哪些序号的球是唯一确定的。这些球放入盒子的顺序有\(a_i!\)种,但是只对应一种情况,所以我们要除去重复的情况。
那么\(D!\sum_{a_i\geq 0,\sum a_i=D}\prod_{i=1}^n\frac{1}{a_i!}\)表示的就是,把\(D\)个有区别的球放入\(n\)个有区别的盒子的方案数。
这个方案数显然有更简单的算法,固定拿球的顺序,比如规定放的第i个球一定标号为i。那么方案数为\(n^D\)
所以$$\sum_{a_i\geq 0,\sum a_i=D}\prod_{i=1}^n\frac{1}{a_i!} =\frac{n^D}{D!}$$
回到我们要求的答案,这里的\(a_i\)是有限制的,要求不小于\(k\).
这个限制可以通过容斥解决,即限制某些位置的\(a_i\)必须小于\(k\),其他地方无限制。
那么我们把这两块地方分开算,必须小于\(k\)的地方直接除上\(a_i!\),无限制的地方直接用公式。注意到取了小于\(k\)的地方后,无限制地方的和(就是公式中的\(D\))会变化,并且小于\(k\)的个数会影响容斥系数,这些用\(Dp\)来记录即可。
设\(F[i][j]\)表示有\(i\)个地方小于\(k\),这些小于\(k\)的和是\(j\),里面是每个方案的贡献。
算完\(F[i][j]\)后枚举\(i\)和\(j\),与没有限制的地方进行合并,容斥计算答案。
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int Mod=998244353;
int n,K,D,Ans,C[51][51];
int Fac[51],Inv[51],F[51][2501];
int Pow(int Down,int Up)
{ int Ret=1,Now=Down;
for(;Up>=1;Up>>=1) Up&1?Ret=1ll*Ret*Now%Mod:0,Now=1ll*Now*Now%Mod;
return Ret;
}
void Prepare()
{ F[0][0]=1,Fac[0]=1,C[0][0]=1;
for(int i=1;i<=K;i++) Fac[i]=1ll*Fac[i-1]*i%Mod;
Inv[K]=Pow(Fac[K],Mod-2);
for(int i=K-1;i>=0;i--) Inv[i]=1ll*Inv[i+1]*(i+1)%Mod;
for(int i=1;i<=n;i++)
for(int j=0;j<=i;j++) C[i][j]=(j?(C[i-1][j]+C[i-1][j-1])%Mod:1);
}
int Calc(int Up,int Down)
{ int Ret=1;
for(int i=Up+1;i<=Down;i++) Ret=1ll*Ret*i%Mod;
return Pow(Ret,Mod-2);
}
int main()
{ scanf("%d%d%d",&n,&K,&D),Prepare();
for(int i=1;i<=n;i++)
for(int j=0;j<=n*K;j++)
{ for(int k=0;k<K&&k<=j;k++)
F[i][j]=(F[i][j]+1ll*F[i-1][j-k]*Inv[k])%Mod;
}
for(int i=0;i<=n;i++)
for(int j=0,Nd;j<=n*K;j++)
Nd=D-j+n*K,Ans=(Ans+(i&1?-1ll:1ll)*F[i][j]*C[n][i]%Mod*Pow(n-i,Nd)%Mod*Calc(D,Nd))%Mod;
printf("%d\n",(Ans+Mod)%Mod);
}
H
题目链接
简要题解
通过观察这个新定义运算,我们可以发现
我们需要求的是$$b_i=\sum_{1\leq j,k \leq n,j\bigotimes k=i }a_j*k^c$$
显然不能直接枚举\(j,k\),事实上枚举gcd是常见的套路。
我们令\(x=\frac{j}{gcd(j,k)},y=\frac{k}{gcd(j,k)}\),\(m=min(\lfloor\frac{n}{x}\rfloor,\lfloor\frac{n}{y}\rfloor)\),那么
我们可以预处理出\(n\)以内每个数的\(c\)次幂,后面那个求和式也可以预处理,前面枚举\(x\)和\(y\)的复杂度是\(O(nlogn)\)的
总时间复杂度为\(O(nlogn)\)
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+10;
const int Mod=998244353;
int n,C,Ans,A[MAXN],B[MAXN];
int Ind[MAXN];
vector<int>Sum[MAXN];
int Read()
{ int a=0,c=1; char b=getchar();
while(b!='-'&&(b<'0'||b>'9')) b=getchar();
if(b=='-') c=-1,b=getchar();
while(b>='0'&&b<='9') a=a*10+b-48,b=getchar();
return a*c;
}
int Pow(int Down,int Up)
{ int Ret=1,Now=Down;
for(;Up>=1;Up>>=1) Up&1?Ret=1ll*Ret*Now%Mod:0,Now=1ll*Now*Now%Mod;
return Ret;
}
int Min(int A,int B){ return A<B?A:B; }
int Gcd(int A,int B){ return B?Gcd(B,A%B):A; }
int main()
{ n=Read(),C=Read();
for(int i=1;i<=n;i++) A[i]=Read();
for(int i=1;i<=n;i++) Ind[i]=Pow(i,C);
for(int i=1,Ns,Nd;i<=n;i++)
{ Sum[i].push_back(0),Ns=0;
for(int j=1;j<=n/i;j++)
Ns=(Ns+1ll*A[i*j]*Ind[j])%Mod,Sum[i].push_back(Ns);
}
for(int i=1;i<=n;i++)
for(int j=1,Top,Nd;j<=n/i;j++)
{ if(Gcd(i,j)>1) continue ;
Top=Min(n/i,n/j),B[i*j]=(B[i*j]+1ll*Sum[i][Top]*Ind[j])%Mod;
}
for(int i=1;i<=n;i++) Ans^=B[i];
printf("%d\n",Ans);
}