題目傳送門:https://www.luogu.org/problemnew/show/P4260
題目分析:一道很好的題,既不是無腦的算法套路題,也不是單純的推式子題。因此我講得詳細一些。比賽的時候我因爲時間問題沒有看這題,後來補了題面,花了一節數學課自己推出了一些東西( 的做法)。後來看了官方題解,發現了一種關於組合數前綴和的新姿勢QAQ。
首先,題面給你的p是沒用的,就是用來混淆視聽。我們只需要算出所有方案的得分之和,最後再除以 即可。接下來我們想知道,對於每一個 ,最終得分爲k的方案數是多少?
不妨假設 。首先考慮 的方案數,換句話說就是Alice得分序列的前綴和都爲非負的方案數。這是一個經典的類似Catalan數的問題。我們可以把Alice的得分序列轉化成平面上從(0,0)到(n,m)的一條路徑,橫着走一步代表出現了一個1,豎着走代表-1:
那麼,不碰到y=x+1這條直線的路徑就是合法的方案(藍色路徑),我們可以用總方案數 減去不合法的方案數求得。對於一條不合法的路徑(綠色路徑),我們找出它第一次碰到y=x+1的位置,然後翻轉前面的這段路徑(粉色路徑),它就變成了一條從(-1,1)到(n,m)的路徑。可以證明不合法的方案翻轉後都能一一對應這樣的路徑,所以合法的方案數爲 。
那麼如果 呢?我們發現,假設Alice得分序列的前綴和最小值爲-1,那麼她在第一次出現-1的時候,得分會變成0,所以後面的-1就會變爲0分,這樣最終得分就是n-m+1。換句話說,我們要求的就是碰到了y=x+1,但沒碰到y=x+2的路徑條數。由類似上面的方法可得,其值爲 。
於是我們推出了最終答案的式子:
通過錯位相減和一些轉換,我們發現:
同理,當 時:
預處理階乘和其逆元之後,求組合數就是 的。現在的問題變成了如何快速求這個東西:
根據楊輝三角 ,我們可以推出:
那麼求 就相當於詢問平面上點 處的值,而我們可以用 的時間移動到某個已知點的相鄰點。這是個經典的問題,可以用分塊或者莫隊解決。雖然它們都是 的,但我一開始寫分塊只拿了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;
}