「EZEC」 Round 6 周年欢乐赛 div1-D 0-1 Trie

真的是高质量好题啊,让我这种组合数学不好的蒟蒻得到了锻炼。

我的做法比较生草:

考虑三个限制条件:

  • 第一位必须是 \(0\)

  • 不能有 \(1\) 相邻。

  • 最后一位必须是 \(1\)

先强制钦定每个 \(1\) 前面有一个 \(0\),将它们捆绑在一起。那么我们现在有了 \(n\)\(01\)\(m-n\)\(0\)

此时无论怎么组合,前面两个条件就已经满足了。

以下我们称 含有 \(n\)\(01\)\(m\)\(0\) 的集合 内部元素 通过排列组合得到的所有方案放到 trie 树上后的节点个数 为该集合的 贡献

\(f_{n,m}\) 表示含有 \(n\)\(01\)\(m\)\(0\) 的集合的 贡献

首先有很明显的边界:

\[\left\{\begin{matrix} f_{n,0}=2n \\ f_{1,m}=m+2 \end{matrix}\right. \]

同时 \(f_{0,m},m>0\) 无定义,因为不满足第三条限制。

以及递推式:\(f_{n,m}=f_{n-1,m}+f_{n,m-1}+2,n>1,m>0\)

那么我们现在就得到了 \(f\) 的一些值(下标从零开始):

\[\begin{matrix} 0 & \times & \times & \times\\ 2 & 3 & 4 &5 \\ 4 & 9 & 15 & \cdots\\ 6 & 17 & 34 & \cdots \end{matrix} \]

为了方便,以下用 \((x,y)\) 对应 \(f\) 的二维矩阵中第 \(x\) 行第 \(y\) 列的元素。

考虑那个转移,实质上是:将一个要求的元素 \((x,y)\) 分裂成向上和向左的两个元素 \((x-1,y)\)\((x,y-1)\),以及产生一个常数项贡献 \(2\)

特殊的,第 \(1\) 行和第 \(0\)在定义中 不能分裂。

不断进行上述过程,会得到若干个 \((1,i),i\in[1,m]\)\((j,0),j\in[2,n]\) 这样的元素。

比较 Simple 的想法就是,直接枚举每个边界元素,计算其贡献,对于常数项系数 \(2\),其贡献发生在每一次分裂的过程中。而每次分裂会使得个数 \(+1\),那么就可以用总个数 \(sum\)\(1\)(一开始的一个元素 \((n,m)\)),来获得 \(2\) 做贡献的次数。

这样就有 \(60\) 分的好成绩。

考虑到我们不能将贡献压缩到常数个点上的原因,就是这个边界上不能转移。

接下来,我们 强制 其转移,也就是将边界各往上往左扩展一格。为了符合转移式,第 \(0\) 行的元素 \((0,i),i>0\) 全部设为 \(-1\),第 \(-1\) 列的元素全部设为 \(0\)。然后我们就可以强制把上一步中 \((1,i),i\in (1,m]\) 的所有点往 \((1,1)\) 转移,同时过程中会产生若干个值为 \(-1\) 的元素。对于另一边也是一样的做法,可以将所有的贡献集中到 \((2,0)\) 这个元素上面。

然后对于 \(0\)\(-1\) 的个数计算,都是形如 \(\sum_{i=0}^r \binom{n}{i}\) 的形式,可以直接将其写为 \(\binom{n+r+1}{r}\)

那么这里就可以 \(\operatorname O(1)\) 求得,卢卡斯求组合数最多递归三层,所以是常数级别的(确信)。

以下包含了 \(30/60/100\) 的代码(前两部分注释掉了)

#include <stdio.h>
#define LL long long
using namespace std;
const int Rea=1e5+3;
struct Rin
{
    char c;
    inline char gc()
    {
        static char rea[Rea];
        static char *head,*tail;
        return head==tail&&(tail=(head=rea)+fread(rea,1,Rea,stdin),head==tail)?EOF:*head++;
    }
    inline Rin&operator >>(LL &x)
    {
        x=0;
        bool tag=false;
        for(c=gc();c>'9'||c<'0';c=gc())if(c=='-'){c=gc();tag=true;break;}
        for(;c>='0'&&c<='9';c=gc())x=(x<<1)+(x<<3)+(c^'0');
        if(tag)x=-x;
        return *this;
    }
    inline Rin&operator >>(int &x)
    {
        x=0;
        bool tag=false;
        for(c=gc();c>'9'||c<'0';c=gc())if(c=='-'){c=gc();tag=true;break;}
        for(;c>='0'&&c<='9';c=gc())x=(x<<1)+(x<<3)+(c^'0');
        if(tag)x=-x;
        return *this;
    }
}rin;

/* const int M=1e3+3;
const int M=18888913;
int f[M][M];
inline int dfs(int n,int m)
{
    if(n<0||m<0)return 0;
    if(m==0)return (n<<1)%M;
    if(f[n][m])return f[n][m];
    if(n>1)f[n][m]+=2+dfs(n-1,m);
    if(m)f[n][m]+=1+dfs(n,m-1)-(n>1);
    f[n][m]%=M;
    return f[n][m];
} */

const int M=18888913;
int sl[M];
int sr[M];
inline int prpr(int x,int y){return 1LL*x*y%M;}
inline int ksm(int x,int y){int ans=1;for(;y;y>>=1){if(y&1)ans=prpr(ans,x);x=prpr(x,x);}return ans;}
inline int C(LL a,LL b)
{
    if(a<b)return 0;
    if(a<M)return prpr(sl[a],prpr(sr[b],sr[a-b]));
    return prpr(C(a/M,b/M),C(a%M,b%M));
}
inline int work()
{
    // int n=rin(),m=rin();return dfs(n,m-n);
    LL n,m;
    rin>>n>>m;
    if(n>m)return 0;m-=n;
    if(n==1)return (m+2)%M;
    if(m==0)return (n<<1)%M;
    int ans=0;
    int sum=0;
    // for(int i=1;i<=m;i++)ans=(ans+prpr(i+2,C(n-2+m-i,n-2)))%M,sum=(sum+C(n-2+m-i,n-2))%M;
    // for(int i=2;i<=n;i++)ans=(ans+prpr(i<<1,C(n-i+m-1,m-1)))%M,sum=(sum+C(n-i+m-1,m-1))%M;
    // ans=(ans+prpr(sum-1,2))%M;
    ans=C(n-1+m-1,m-1)*3+((C(n-2+m,m))<<2);
    ans+=C(n+m-2,m-2);
    sum+=C(m+n-2,n-3);
    sum+=C(n-1+m-1,m-1);
    sum+=C(n+m-2,m);
    ans+=(sum-1<<1);
    return (ans%M+M)%M;
}
int main()
{
    int ans=0;
    /* for(int i=1;i<M;i++)f[i][0]=i<<1;
    for(int i=1;i<M;i++)f[1][i]=i+2;
    for(int i=2;i<M;i++)for(int j=1;j<M;j++)f[i][j]=(f[i-1][j]+f[i][j-1]+2)%M; */
    sl[0]=sr[0]=1;
    for(int i=1;i<M;i++)sl[i]=prpr(sl[i-1],i);
    sr[M-1]=ksm(sl[M-1],M-2);for(int i=M-2;i>=1;i--)sr[i]=prpr(sr[i+1],i+1);
    int T;
    for(rin>>T;T;T--)ans^=work();
    printf("%d\n",ans);
    return 0;
}
posted @ 2021-02-20 19:14  zjjws  阅读(94)  评论(0)    收藏  举报