- 有一张(n imes m)的网格图,每个位置只能填入([0,m])。
- 要求满足每个位置((i,j))比((i,j+1))和((i-1,j+1))都小,求方案数。
- (n,mle10^6)
动态规划
首先发现总共只有(m+1)种数,而每一行都要填(m)个递增的数,因此一行的状态实际上只需要考虑哪个数没有即可。
即,设(f_{i,j})表示第(i)行没有的数是(j)的方案数。
考虑转移,画画图发现(kle j+1)的(f_{i-1,k})都是可以转移的,而更大的(k)就不满足条件了。
因此得出转移方程:
[f_{i,j}=sum_{k=0}^{j+1}f_{i-1,k}=f_{i,j-1}+f_{i-1,j+1}
]
坐标系走路的转化
把转移表示到图上去:(图画得很丑,但我尽力了。。。)
发现斜着的转移很难表示,所以我们先把它掰直,变成:
那么也就相当于我们不能触碰到下图中的两条蓝线(A)和(B):
容斥
(画不动图了,而且估计我画得这么丑也没人看,因此就不放了)
经典容斥问题。
以第一条线(A)为例,我们找到终点关于它的对称点,那么抵达对称点的方案等同于以(A)结尾或以(AB)结尾的非法路径。同理以(B)为例,抵达关于它的对称点的方案等同于以(B)结尾或以(BA)结尾的非法路径。
但它们之间的非法路径肯定存在包含关系,因为以(A)结尾也就包含了以(BA)结尾,因此需要容斥。
具体地,依旧以第一条线(A)为例,我们找到终点关于它的对称点,用答案减去这一部分方案数,然后找到这个对称点关于第二条线(B)的对称点,给答案加上这一部分方案数,然后再回去给当前对称点找到第一条线的对称点减去方案数,以此类推,直至越界(某一维小于(0))。第二条线则反之。
显然终点坐标为((n+m+1,n))。而关于对称点,有解析式(A:y=x+1,B:y=x-m-2),所以((x,y))关于(a,b)的对称点分别为((y-1,x+1),(y+m+2,x-m-2))。
代码实现很简单,但思维难度真的高。
代码:(O(n))
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 1000000
#define X 1000000007
using namespace std;
int n,m;I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
int Fac[3*N+5],IFac[3*N+5];I void InitFac(CI S)
{
RI i;for(Fac[0]=i=1;i<=S;++i) Fac[i]=1LL*Fac[i-1]*i%X;//预处理阶乘
for(IFac[i=S]=QP(Fac[S],X-2);i;--i) IFac[i-1]=1LL*IFac[i]*i%X;//预处理阶乘逆元
}
I void A(int& x,int& y) {RI a=y-1,b=x+1;x=a,y=b;}//把点按A翻转
I void B(int& x,int& y) {RI a=y+m+2,b=x-m-2;x=a,y=b;}//把点按B翻转
int main()
{
#define Calc(x,y) ((x)>=0&&(y)>=0?1LL*Fac[(x)+(y)]*IFac[x]%X*IFac[y]%X:0)//到达(x,y)的方案数,就是C(x+y,x)
scanf("%d%d",&n,&m),InitFac(3*max(n,m)+1);RI x,y,t=Calc(n+m+1,n);
x=n+m+1,y=n;W(x>=0&&y>=0) A(x,y),t=(t-Calc(x,y)+X)%X,x>=0&&y>=0&&(B(x,y),t=(t+Calc(x,y))%X);//对于A的容斥
x=n+m+1,y=n;W(x>=0&&y>=0) B(x,y),t=(t-Calc(x,y)+X)%X,x>=0&&y>=0&&(A(x,y),t=(t+Calc(x,y))%X);//对于B的容斥
return printf("%d
",t),0;
}