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

A.Browser Games

题目链接

Browser Games

简要题解

这道题卡空间,因此处理起来需要多费功夫。
我们先考虑不卡空间的话,该怎么做。
不难想到,首先要对所有串建一棵\(Trie\)树,然后在\(Trie\)树上维护一些东西。

具体地说,我们先将所有字符串染成黑色,然后第\(i\)天会把第\(i\)个串染成白色,并计算答案。
\(Trie\)树上的每个节点,记录当前时刻,有多少个黑串和白串经过。
我们的任务是选出最少的关键点,使得每个白串上都必须要有关键点,每个黑串上都不能有关键点。
那么对于每一天来说,我们首先将当天串的串尾记为关键点,然后将关键点尽可能地往根移动即可。

如果限制了空间怎么办?
我们观察发现,对于\(Trie\)树上的某个节点来说,如果它只有一个儿子,那么它的儿子肯定不会成为关键点,因为我们会把关键点尽可能往根移动。
于是我们就可以把这个点和它的儿子缩成一个点。

空间复杂度分析,由于任何一个串都不是其他串的前缀,所以在\(Trie\)树上,每个点一定会对应\(1\)个叶子结点。
我们倒过来考虑,已经有了\(n\)个叶子结点,将它们不断合并,最终合并成一棵\(Trie\)树。
不难发现,每次合并两棵子树,只会增加一个结点,因此整棵树只会有大约\(2*n\)个结点。

关于如何建出这棵\(Trie\)树,首先对所有串按字典序排序,那么经过\(Trie\)树上某个节点的串一定是一段连续的区间。
这样我们就能够轻易记录,到达某个结点时,还有哪些串了,递归建树即可。
具体实现,这里的方法是,用\(set\)记录了每个点的儿子。
代码如下:

#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int MAXN=1e5+1;
char Str[MAXN][101];
int Ans,n,Ss,End[MAXN],Ord[MAXN];
vector<int>Bin,Fa,Pass;
vector<set<int>>Son;
vector<bool>Col;
set<int>Zero;
bool cmp(int A,int B){   return strcmp(Str[A]+1,Str[B]+1)<0;   }
void New(int Nf,int Np){   Son.pb(Zero),Fa.pb(Nf),Pass.pb(Np),Col.pb(0);   }
void Build(int S,int D,int Le,int Ri)
{   if(Le==Ri) return End[Ord[Le]]=S,(void)0;
    for(int i=Le,j=i;i<=Ri;i=++j)
    {   while(j+1<=Ri&&Str[Ord[j+1]][D]==Str[Ord[i]][D]) j++;
        while(Str[Ord[i]][D]==0&&i<=j) i++;
        if(i==Le&&j==Ri&&S) Build(S,D+1,Le,Ri);
        else if(i<=j) New(S,j-i+1),Build(++Ss,D+1,i,j);
    }
}
void Add(int Np){   Col[Np]=1,Ans++,Son[Fa[Np]].insert(Np);   }
void Del(int Np){   Col[Np]=0,Ans--,Son[Fa[Np]].erase(Np);   }
int main()
{   scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%s",Str[i]+1),Ord[i]=i;
    sort(Ord+1,Ord+n+1,cmp),New(0,n),Build(0,1,1,n);
    for(int i=1;i<=n;i++)
    {   Add(End[i]);
        for(int j=End[i];j;j=Fa[j]) Pass[j]--;
        for(int j=End[i];j;j=Fa[j])
            if(Pass[Fa[j]]) j=0;
            else if(Col[Fa[j]]) Del(j);
            else
            {   for(int v:Son[Fa[j]]) Bin.pb(v);
                for(int v:Bin) Del(v);
                Bin.clear(),Add(Fa[j]);
            }
        printf("%d\n",Ans);
    }
}

G.Game of Death

题目链接

Game of Death

简要题解

我们设\(S\)表示某种情况下,被射中的人的集合,再设\(F[S]\)表示,该情况发生的概率。
这个直接求不太方便,但是实际上,这道题是二项式反演的经典形式。
具体地说,我们设\(G[S]\)表示被射中的人是\(S\)子集的概率,那么根据二项式反演,我们有

\[F[S]=\sum_{T\subseteq S}(-1)^{|S|-|T|}G[T] \]

不难发现,\(F[S]\)\(G[S]\)的值只和\(|S|\)有关,于是我们重新设\(F[i]\)表示,恰好\(i\)个人被射中的概率,\(G[i]\)表示不超过\(i\)个人被射中的概率。

\[F[i]=\sum_{j=0}^i(-1)^{i-j}*C_i^j*G[j] \]

这个式子可以拆成多项式卷积的形式进行优化,现在我们只需要知道\(G[i]\)的值了。

考虑如何求\(G[i]\),既然是不超过\(i\)个人死,我们就只需要保证射中的子弹在这\(i\)个人里面即可。
对于外面的\(n-i\)个人,要么没射中,要么射中了\(i\)个人中的某一个,因此概率为\((1-p+p*\frac{i}{n-1})^{n-i}\)
对于里面的\(i\)个人,只是不能射自己,其他的分析同上,概率为\((1-p+p*\frac{i-1}{n-1})^i\)
于是

\[G[i]=(1-p+p*\frac{i-1}{n-1})^i*(1-p+p*\frac{i}{n-1})^{n-i} \]

时间复杂度\(O(n*logn)\)
代码如下:

#include<bits/stdc++.h>
using namespace std;
const int Mod=998244353;
const int MAXN=3e5+10;
int n,P,Q,Inv[MAXN],Fac[MAXN],F[MAXN],G[MAXN];
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 Add(int A,int B){   return A+=B,A>=Mod?A-Mod:A;   }
namespace Poly
{   int A[1<<20],B[1<<20],Rader[1<<20],Len,Ms,End,Invl;
    void NTT(int *P,int K)
    {   for(int i=0;i<Len;i++)
            if(i<Rader[i]) swap(P[i],P[Rader[i]]);
        for(int i=1;i<Len;i<<=1)
        {   int Euler=Pow(3,(Mod-1)/(i<<1));
            if(K<0) Euler=Pow(Euler,Mod-2);
            for(int Pos=(i<<1),j=0;j<Len;j+=Pos)
            {   int Wi=1;
                for(int k=0;k<i;k++,Wi=1ll*Wi*Euler%Mod)
                {   int X=P[k+j],Y=1ll*Wi*P[i+j+k]%Mod;
                    P[k+j]=Add(X,Y),P[i+j+k]=Add(X,Mod-Y);
                }
            }
        }
        if(K>0) return ;
        Invl=Pow(Len,Mod-2);
        for(int i=0;i<Len;i++) P[i]=1ll*P[i]*Invl%Mod;
    }
    void Prepare()
    {   End=n*2,Ms=-1;
        for(Len=1;Len<=End;Len<<=1) Ms++;
        for(int i=0;i<Len;i++) Rader[i]=((Rader[i>>1]>>1)|((i&1)<<Ms));
    }
    void Work()
    {   for(int i=0;i<=n;i++) A[i]=i&1?Mod-Inv[i]:Inv[i],B[i]=1ll*G[i]*Inv[i]%Mod;
        NTT(A,1),NTT(B,1);
        for(int i=0;i<Len;i++) A[i]=1ll*A[i]*B[i]%Mod;
        NTT(A,-1);
        for(int i=0;i<=n;i++) F[i]=1ll*Fac[i]*A[i]%Mod;
    }
}using namespace Poly;
int C(int A,int B){   return B>A||A<0?0:1ll*Fac[A]*Inv[B]%Mod*Inv[A-B]%Mod;   }
int main()
{   scanf("%d%d%d",&n,&P,&Q),P=1ll*P*Pow(Q,Mod-2)%Mod,Q=Add(Mod-P,1),Fac[0]=1;
    for(int i=1;i<=n;i++) Fac[i]=1ll*Fac[i-1]*i%Mod;
    Inv[n]=Pow(Fac[n],Mod-2);
    for(int i=n-1;i>=0;i--) Inv[i]=1ll*Inv[i+1]*(i+1)%Mod;
    for(int i=0,Invn=1ll*Inv[n-1]*Fac[n-2]%Mod;i<=n;i++)
        G[i]=1ll*Pow((Q+1ll*P*(i-1)%Mod*Invn)%Mod,i)*Pow((Q+1ll*P*i%Mod*Invn)%Mod,n-i)%Mod;
    Prepare(),Work();
    for(int i=n;i>=0;i--) printf("%lld\n",1ll*F[i]*C(n,i)%Mod);
}
posted @ 2021-08-26 21:30  Alkaid~  阅读(83)  评论(0编辑  收藏  举报