洛谷P4260:[Code+#3]博弈论与概率统计 (组合数学+莫队/分块)

题目传送门:https://www.luogu.org/problemnew/show/P4260


题目分析:一道很好的题,既不是无脑的算法套路题,也不是单纯的推式子题。因此我讲得详细一些。比赛的时候我因为时间问题没有看这题,后来补了题面,花了一节数学课自己推出了一些东西(O(Tn) 的做法)。后来看了官方题解,发现了一种关于组合数前缀和的新姿势QAQ。

首先,题面给你的p是没用的,就是用来混淆视听。我们只需要算出所有方案的得分之和,最后再除以Cn+mn 即可。接下来我们想知道,对于每一个k(k[nm,n]) ,最终得分为k的方案数是多少?

不妨假设mn 。首先考虑k=nm 的方案数,换句话说就是Alice得分序列的前缀和都为非负的方案数。这是一个经典的类似Catalan数的问题。我们可以把Alice的得分序列转化成平面上从(0,0)到(n,m)的一条路径,横着走一步代表出现了一个1,竖着走代表-1:

那么,不碰到y=x+1这条直线的路径就是合法的方案(蓝色路径),我们可以用总方案数Cn+mn 减去不合法的方案数求得。对于一条不合法的路径(绿色路径),我们找出它第一次碰到y=x+1的位置,然后翻转前面的这段路径(粉色路径),它就变成了一条从(-1,1)到(n,m)的路径。可以证明不合法的方案翻转后都能一一对应这样的路径,所以合法的方案数为Cn+mnCn+mn+1

那么如果k=nm+1 呢?我们发现,假设Alice得分序列的前缀和最小值为-1,那么她在第一次出现-1的时候,得分会变成0,所以后面的-1就会变为0分,这样最终得分就是n-m+1。换句话说,我们要求的就是碰到了y=x+1,但没碰到y=x+2的路径条数。由类似上面的方法可得,其值为Cn+mn+1Cn+mn+2

于是我们推出了最终答案的式子:

ans=i=0m(Cn+mn+iCn+mn+i+1)(nm+i)

通过错位相减和一些转换,我们发现:

ans=(nm)Cn+mn+i=0m1Cn+mi

同理,当n<m 时:

ans=i=0n1Cn+mi

预处理阶乘和其逆元之后,求组合数就是O(1) 的。现在的问题变成了如何快速求这个东西:

F(n,k)=i=0kCni

根据杨辉三角Cij=Ci1j1+Ci1j ,我们可以推出:

F(n+1,k)=2F(n,k)Cnk

那么求F(n,k) 就相当于询问平面上点(n,k) 处的值,而我们可以用O(1) 的时间移动到某个已知点的相邻点。这是个经典的问题,可以用分块或者莫队解决。虽然它们都是O(NN) 的,但我一开始写分块只拿了70。原因是它常数又大,空间消耗又多,于是果断换了莫队。


CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=250100;
const int NN=250000;
const int M=1000000007;
typedef long long LL;

struct data
{
    int id,N,K;
} ask[maxn];

LL fac[maxn];
LL nfac[maxn];

int ans[maxn];
int Div[maxn];
int t,n,m;
int sn=501;

void Preparation()
{
    fac[0]=1;
    for (LL i=1; i<=NN; i++) fac[i]=fac[i-1]*i%M;
    nfac[0]=nfac[1]=1;
    for (LL i=2; i<=NN; i++)
    {
        LL x=M/i,y=M%i;
        nfac[i]=M-x*nfac[y]%M;
    }
    for (LL i=2; i<=NN; i++) nfac[i]=nfac[i-1]*nfac[i]%M;
}

int C(int nn,int mm)
{
    if (mm>nn) return 0;
    LL val=fac[nn];
    val=val*nfac[mm]%M;
    val=val*nfac[nn-mm]%M;
    return val;
}

bool Comp(data x,data y)
{
    int a=x.N/sn;
    int b=y.N/sn;
    return ( a<b || ( a==b && x.K<y.K ) );
}

int Mod(int x)
{
    if (x>=M) return x-M;
    else return x;
}

int Dec(int x)
{
    if (x<0) return x+M;
    else return x;
}

int main()
{
    freopen("D.in","r",stdin);
    freopen("D.out","w",stdout);

    Preparation();
    int p;
    scanf("%d%d",&t,&p);
    for (int i=1; i<=t; i++)
    {
        scanf("%d%d",&n,&m);
        if (n>=m) ans[i]=(long long)(n-m)*C(n+m,n)%M,ask[i].K=m-1;
        else ask[i].K=n-1;
        ask[i].N=n+m;
        ask[i].id=i;
        Div[i]=nfac[n+m];
        Div[i]=(long long)Div[i]*fac[n]%M;
        Div[i]=(long long)Div[i]*fac[m]%M;
    }

    sort(ask+1,ask+t+1,Comp);
    ask[0].N=-1e9;
    int val=0;
    for (int i=1; i<=t; i++)
    {
        if (ask[i].K==-1) continue;
        if ( ask[i-1].K==-1 || ask[i-1].N/sn<ask[i].N/sn )
        {
            n=ask[i].N;
            m=ask[i].K;
            val=0;
            for (int i=0; i<=m; i++) val=Mod(val+ C(n,i) );
        }
        else
        {
            while (n<ask[i].N) val=Dec( Mod(val<<1)-C(n,m) ),++n;
            while (n>ask[i].N) --n,val=(long long)Mod(val+ C(n,m) )*nfac[2]%M;
            while (m<ask[i].K) ++m,val=Mod(val+ C(n,m) );
        }
        int x=ask[i].id;
        ans[x]=Mod(ans[x]+val);
    }

    for (int i=1; i<=t; i++) ans[i]=(long long)ans[i]*Div[i]%M;
    for (int i=1; i<=t; i++) printf("%d\n",ans[i]);
    return 0;
}
发布了160 篇原创文章 · 获赞 76 · 访问量 10万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章