题目描述:
n
×
m
n\times m
n×m的网格,每个格子填
[
0
,
m
]
[0,m]
[0,m]之一的整数,要求
x
i
,
j
<
x
i
,
j
+
1
,
x
i
,
j
<
x
i
−
1
,
j
+
1
x_{i,j}<x_{i,j+1},x_{i,j}<x_{i-1,j+1}
xi,j<xi,j+1,xi,j<xi−1,j+1。问填数方案数。
n
,
m
≤
1
0
6
n,m\le10^6
n,m≤106,mod 109+7
题目分析:
毒瘤神题。
经过一堆神乎其技的转化(详见这位dalao的博客)
我们得到了这样一张图(摘自上面的博客)
题目的答案就是从
(
0
,
0
)
(0,0)
(0,0)不触碰两条直线走到
(
n
,
n
+
m
+
1
)
(n,n+m+1)
(n,n+m+1)的方案数。
我们知道从(0,0)走到一个点(i,j)的方案数是
C
i
+
j
i
C_{i+j}^i
Ci+ji,并且可以通过翻折求出必经过一条直线到达一个点的方案数。
譬如(x,y),沿着直线y=x+1翻折,就相当于先下移1,再交换横纵坐标(沿y=x翻折的规律),再上移1,变为(y-1,x+1)。那么必经直线y=x+1到达点(x,y)的方案数就等于到达(y-1,x+1)的方案数。
同理,沿着直线y=x-(m+2)翻折,相当于先上移(m+2),再交换横纵坐标,再下移(m+2),变为(y+m+2,x-(m+2))。
那么我们尝试用到达(n,n+m+1)的方案减去必经上面的直线到达它的方案,再减去必经下面的直线到达它的方案。
但是这样会减多,因为一条路径可能多次经过了两条直线。
那么我们对方案进行分类,减去先经过上面的直线到达它的方案,以及先经过下面的直线到达它的方案。
先经过上面的直线到达它的方案怎么求呢,我只会求必经直线到达它的方案啊QWQ。
考虑用必经上面的直线到达它的方案数再减去这些方案中先经过下面的直线到达已翻折过的点的方案数,求先经过下面的直线到达已翻折过的点的方案数就是一个子问题了。
于是做法就是不断将点(n,n+m+1)沿y=x+1和y=x-(m+2)翻折并减去或加上对应的方案数,直到点的横纵坐标为负数,方案数就始终为0,不必再减了。
Code:
#include<cstdio>
#include<algorithm>
#define maxn 3000005
using namespace std;
const int mod = 1e9+7;
int n,m,fac[maxn],inv[maxn],ans;
void Pre(const int N){
fac[0]=fac[1]=inv[0]=inv[1]=1;
for(int i=2;i<=N;i++) fac[i]=1ll*fac[i-1]*i%mod,inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
for(int i=2;i<=N;i++) inv[i]=1ll*inv[i]*inv[i-1]%mod;
}
inline int C(int n,int m){return n<m||m<0?0:1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;}
inline void flip1(int &x,int &y){swap(x,y),x--,y++;}
inline void flip2(int &x,int &y){swap(x,y),x+=m+2,y-=m+2;}
int main()
{
scanf("%d%d",&n,&m),Pre(n+m+1+n),ans=C(n+m+1+n,n);
for(int x=n+m+1,y=n;x>=0&&y>=0;){
flip1(x,y),ans=(ans+mod-C(x+y,x))%mod;
flip2(x,y),ans=(ans+C(x+y,x))%mod;
}
for(int x=n+m+1,y=n;x>=0&&y>=0;){
flip2(x,y),ans=(ans+mod-C(x+y,x))%mod;
flip1(x,y),ans=(ans+C(x+y,x))%mod;
}
printf("%d\n",ans);
}