题目传送门:https://www.luogu.org/problemnew/show/P4260
题目分析:一道很好的题,既不是无脑的算法套路题,也不是单纯的推式子题。因此我讲得详细一些。比赛的时候我因为时间问题没有看这题,后来补了题面,花了一节数学课自己推出了一些东西( O(Tn) O ( T n ) 的做法)。后来看了官方题解,发现了一种关于组合数前缀和的新姿势QAQ。
首先,题面给你的p是没用的,就是用来混淆视听。我们只需要算出所有方案的得分之和,最后再除以 Cnn+m C n + m n 即可。接下来我们想知道,对于每一个 k(k∈[n−m,n]) k ( k ∈ [ n − m , n ] ) ,最终得分为k的方案数是多少?
不妨假设 m≤n m ≤ n 。首先考虑 k=n−m 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+m−Cn+1n+m C n + m n − C n + m n + 1 。
那么如果 k=n−m+1 k = n − m + 1 呢?我们发现,假设Alice得分序列的前缀和最小值为-1,那么她在第一次出现-1的时候,得分会变成0,所以后面的-1就会变为0分,这样最终得分就是n-m+1。换句话说,我们要求的就是碰到了y=x+1,但没碰到y=x+2的路径条数。由类似上面的方法可得,其值为 Cn+1n+m−Cn+2n+m C n + m n + 1 − C n + m n + 2 。
于是我们推出了最终答案的式子:
通过错位相减和一些转换,我们发现:
同理,当 n<m n < m 时:
预处理阶乘和其逆元之后,求组合数就是 O(1) O ( 1 ) 的。现在的问题变成了如何快速求这个东西:
根据杨辉三角 Cji=Cj−1i−1+Cji−1 C i j = C i − 1 j − 1 + C i − 1 j ,我们可以推出:
那么求 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;
}