洛谷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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章