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

23 篇文章 0 订阅
4 篇文章 0 订阅

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


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

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

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

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

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

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

ans=i=0m(Cn+in+mCn+i+1n+m)(nm+i) a n s = ∑ i = 0 m ( C n + m n + i − C n + m n + i + 1 ) ( n − m + i )

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

ans=(nm)Cnn+m+i=0m1Cin+m a n s = ( n − m ) C n + m n + ∑ i = 0 m − 1 C n + m i

同理,当 n<m n < m 时:

ans=i=0n1Cin+m a n s = ∑ i = 0 n − 1 C n + m i

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

F(n,k)=i=0kCin F ( n , k ) = ∑ i = 0 k C n i

根据杨辉三角 Cji=Cj1i1+Cji1 C i j = C i − 1 j − 1 + C i − 1 j ,我们可以推出:

F(n+1,k)=2F(n,k)Ckn F ( n + 1 , k ) = 2 F ( n , k ) − C n k

那么求 F(n,k) F ( n , k ) 就相当于询问平面上点 (n,k) ( n , k ) 处的值,而我们可以用 O(1) O ( 1 ) 的时间移动到某个已知点的相邻点。这是个经典的问题,可以用分块或者莫队解决。虽然它们都是 O(NN) O ( N N ) 的,但我一开始写分块只拿了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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值