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);
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM