2021牛客暑期多校训练营4 部分题题解

B.Sample Game

题目链接

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]$$

\[Ans=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)=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

题目链接

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

题目链接

Product

简要题解

我们先用数学语言将答案表示出来。

\[Ans=D!\sum_{a_i\geq 0,\sum a_i=D}\prod_{i=1}^n\frac{1}{(a_i+k)!} \]

\((a_i+k)!\)不利于推导公式,我们换一种写法

\[Ans=D!\sum_{a_i\geq k,\sum a_i=D+nk}\prod_{i=1}^n\frac{1}{a_i!} \]

如果\(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

题目链接

Convolution

简要题解

通过观察这个新定义运算,我们可以发现

\[x\bigotimes y=\frac{xy}{gcd(x,y)^2} \]

我们需要求的是$$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)\),那么

\[b_i =\sum_{xy=i,gcd(x,y)=1} \sum_{d=1}^{m}a_{xd} (yd)^c \]

\[b_i =\sum_{xy=i,gcd(x,y)=1} y^c \sum_{d=1}^{m}a_{xd} d^c \]

我们可以预处理出\(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);
}
posted @ 2021-07-27 14:28  Alkaid~  阅读(152)  评论(3编辑  收藏  举报