ZROI Easy Sum(生成函数,分块,dp,组合,多项式)

本文介绍了一种使用组合数学和生成函数解决路径计数问题的方法。通过坐标系翻转和分块思想,利用多项式卷积进行高效计算,最终得出所有点到指定位置的路径数量总和。

解题思路

首先,考虑Ca+b−kaC_{a+b-k}^aCa+bka的组合意义,相当于是在二维平面上,从(a,b)(a,b)(a,b)走到(0,k)(0,k)(0,k),每次可以向下或者向左走的方案数的方案数

因此,原题等价于对于k=0……n−1,k=0……n-1,k=0n1,求所有点到(0,k)(0,k)(0,k)的方案数之和。

首先可以把坐标系水平翻转一下,这样的话就相当于每次可以向左或者向上走,水平翻转是指(x,y)(x,y)(x,y)变成(x,n−1−y)(x,n-1-y)(x,n1y),这样做是为了方便卷积。

接下来,我们对x这一维分块,取一个整数B,然后设dp[i][j]dp[i][j]dp[i][j],表示从所有点走到(iB,j)(iB,j)(iB,j)的方案数。
不过这样子的话会算重,于是我们换一下,设dp[i][j]dp[i][j]dp[i][j]表示从所有点走到(iB,j)(iB,j)(iB,j),且最后一步向左走的方案数。

首先,考虑如果iBiBiB(i+1)B(i+1)B(i+1)B这段区间内没有点的情况。

我们可以写出一个dpdpdp式子:
dp[i][j]=∑kdp[i+1][k]Cj−k+B−1j−kdp[i][j]=\sum_{k}dp[i+1][k]C_{j-k+B-1}^{j-k}dp[i][j]=kdp[i+1][k]Cjk+B1jk减一是因为最后一步钦定了。
我们可以把它看成两个多项式卷积的形式,其中,第二个多项式为CB−10,CB−1+11,CB−1+22……C_{B-1}^0,C_{B-1+1}^1,C_{B-1+2}^2……CB10,CB1+11,CB1+22
根据经典结论,Ci0,Ci+11……C_i^0,C_{i+1}^1……Ci0,Ci+11的OGF为1(1−x)i+1\frac{1}{(1-x)^{i+1}}(1x)i+11
因此该多项式的OGF为1(1−x)B\frac{1}{(1-x)^{B}}(1x)B1
然后考虑新加的点,那么就会有dp式子:
dp[i][j]=dp[i][j]+Cj−a+l−1j−adp[i][j]=dp[i][j]+C_{j-a+l-1}^{j-a}dp[i][j]=dp[i][j]+Cja+l1ja,其中l表示这个点到x=Bix=Bix=Bi这条直线的距离。
这种转移也可以写成生成函数:
xb1(1−x)lx^b\frac{1}{(1-x)^{l}}xb(1x)l1
综合一下,设F(i,x)=dp[i]F(i,x)=dp[i]F(i,x)=dp[i]的生成函数.
那么F(i,x)=F(i−1,x)×1(1−x)B+∑ixb1(1−x)lF(i,x)=F(i-1,x)\times \frac{1}{(1-x)^{B}}+\sum_ix^b\frac{1}{(1-x)^{l}}F(i,x)=F(i1,x)×(1x)B1+ixb(1x)l1
不过如果就是这个样子的话,那么后面的部分是O(n2)O(n^2)O(n2)
乘法分配律:
F(i,x)=(F(i−1,x)+∑ixb1(1−x)l−B)×1(1−x)BF(i,x)=(F(i-1,x)+\sum_ix^b\frac{1}{(1-x)^{l-B}})\times \frac{1}{(1-x)^{B}}F(i,x)=(F(i1,x)+ixb(1x)lB1)×(1x)B1
F(i,x)=(F(i−1,x)+∑ixb(1−x)B−l)×1(1−x)BF(i,x)=(F(i-1,x)+\sum_ix^b(1-x)^{B-l})\times \frac{1}{(1-x)^{B}}F(i,x)=(F(i1,x)+ixb(1x)Bl)×(1x)B1
这样做的好处是,前面加的部分只需要O(B)O(B)O(B)就可以计算了。
接着我们只需要展开(1−x)B−l,1(1−x)B(1-x)^{B-l}, \frac{1}{(1-x)^{B}}(1x)Bl,(1x)B1然后做卷积就行了。
前面可以二项式定理展开(注意x前面是符号),后面直接套用上边的结论展开就行了。
时间复杂度O(nB+n(n/B)logn)O(nB+n(n/B)logn)O(nB+n(n/B)logn),取B=(nlogn)B=\sqrt (nlogn)B=(nlogn)
最后因为我们的dp钦定最后一步向左走,因此只需要在做一遍前缀和求出最后一步向上走的方案数就行了。
别忘了因为一开始翻转了坐标系,因此最后要再翻回来。

#include<bits/stdc++.h>
using namespace std;
const int N = 3e5+7;
typedef long long LL;
const int mod = 998244353;
LL Pow(LL a,LL b)
{
    LL res=1;
    while(b)
    {
        if(b&1)res=1ll*res*a%mod;
        a=1ll*a*a%mod;
        b>>=1;
    }
    return res;
}
LL G=3,IG=Pow(3,mod-2);
#define Poly vector<LL>
#define len(x) (int)x.size()
#define turn(x,v) x.resize(v)
int tr[N*4];
void Retry(int n)
{
    for(int i=0;i<n;i++)
    tr[i]=((tr[i>>1]>>1)|((i&1)?(n>>1):0));
}
void NTT(Poly &f,int opt)
{
    int n=len(f);
    for(int i=0;i<n;i++)
    if(i<tr[i]) swap(f[i],f[tr[i]]);
    for(int len=2;len<=n;len<<=1)
    {
        int l=(len>>1);
        LL w=Pow(opt==1?G:IG,(mod-1)/len);
        for(int i=0;i<n;i+=len)
        {
            LL g=1;
            for(int j=i;j<i+l;j++)
            {
                LL v=g*f[j+l]%mod;
                f[j+l]=(f[j]-v+mod)%mod;
                f[j]=(f[j]+v)%mod;
                g=1ll*g*w%mod;
            }
        }
    }
}
Poly Mul(Poly f ,Poly g)
{
    int n=len(f),m=len(g);
    int len=1;
    for(;len<=n+m-1;len<<=1);
    Retry(len);turn(f,len);turn(g,len);
    NTT(f,1);NTT(g,1);
    for(int i=0;i<len;i++)
    f[i]=1ll*f[i]*g[i]%mod;
    NTT(f,-1);
    LL invn=Pow(len,mod-2);
    for(int i=0;i<len;i++)
    f[i]=1ll*f[i]*invn%mod;
    turn(f,n);
    return f;
}
int n;
LL fac[N],ifac[N];
void init(int x)
{
    fac[0]=1;
    for(int i=1;i<=x;i++)
    fac[i]=1ll*fac[i-1]*i%mod;
    ifac[x]=Pow(fac[x],mod-2);
    for(int i=x-1;i>=0;i--)
    ifac[i]=(LL)ifac[i+1]*(i+1)%mod;
}
LL C(int a,int b)
{
    if(a<0||b<0||a<b) return 0;
    return (LL)fac[a]*ifac[b]%mod*ifac[a-b]%mod;
}
int a[N],b[N];
vector<int> s[N];
LL pw(int x)
{
    if(x&1) return mod-1;
    return 1;
}
int main()
{
    freopen("easysum.in","r",stdin);
    freopen("easysum.out","w",stdout);
    cin>>n;
    int mx=0;
    for(int i=1;i<=n;i++)
    scanf("%d %d",&a[i],&b[i]),mx=max(mx,a[i]),b[i]=n-1-b[i];
    init(2*n);
    mx=max(mx,1);
    int B=sqrt(mx*log2(mx));
    B=max(B,1);
    int cnt=mx/B;
    for(int i=1;i<=n;i++)
    s[(a[i]/B)].push_back(i);
    Poly F;
    turn(F,n);
    for(int k=cnt;k>=0;k--)
    {
        for(auto x:s[k])
        {
            int l=a[x]%B;
            for(int i=0;i<=B-l;i++)
            if(b[x]+i<n) F[b[x]+i]=(F[b[x]+i]+(LL)pw(i)*C(B-l,i)%mod)%mod;
        }
        Poly G;
        turn(G,n);
        for(int i=0;i<n;i++)
        G[i]=C(B+i-1,i);
        F=Mul(F,G);
    }
    for(int i=1;i<n;i++)
    F[i]=(F[i-1]+F[i])%mod;
    reverse(F.begin(),F.end());
    for(int i=0;i<n;i++)
    printf("%lld ",F[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值