Link
观察到每行只能是在([0,m])中选择一个不出现的数,然后剩下的升序排列。
假如第(i)行不出现的数是(j),那么(i-1)行不出现的数必须是([0,j-1])。
因此我们可以设计一个dp,设(f_{i,j})表示只考虑前(i)行,第(i)行不出现的数是(j)的方案数,那么显然有:
(f_{i,j}=sumlimits_{k=0}^{j+1}f_{i-1,k}=f_{i,j-1}+f_{i-1,j+1})(为了方便转移(j)的上界是(m+1))。
这样初始状态就是(f_{0,0}=1),答案就是(f_{n,m+1})。
考虑转化为格路问题,(f_{n,m+1})就是从((0,0))走到((n,n+m+1)),每次只能向上或向右走,不能碰到(l_1:x-y=0,l_2:x-y+m+1=0)两条直线的方案数。
再设(f(x,y),g(x,y))表示先碰到(l_1,l_2)到达((x,y))的方案数。
利用补集容斥得到(f_{n,m+1}={2n+m+1choose n}-(f(n,n+m+1)+g(n,n+m+1)))。
考虑经典做法对称,(f(n,n+m+1)=f(n+m+2,n-1),g(n,n+m+1)=g(n-1,n+m+2))。
此时(f(x,y),g(x,y))一定满足(y-x<0vee y-x>m+1),因此((0,0))到((x,y))一定会越过边界,即(f(x,y)+g(x,y)={x+ychoose x})。
再次对称可以得到(f(x,y)={x+ychoose x}-g(y-m-2,x+m+2),g(x,y)={x+ychoose x}-f(y+1,x-1))。
这样就能保证递归求解时(f(x,y),g(x,y))都满足(y-x<0vee y-x>m+1)了。
边界条件为(f(x,y)=g(x,y)=0qquad ext{where}quad x<0vee y<0)。
#include<cstdio>
const int P=1000000007;
int n,m,fac[3000007],ifac[3000007];
int dec(int a,int b){return a-=b,a+=a>>31&P;}
int mul(int a,int b){return 1ll*a*b%P;}
int pow(int a,int k){int r=1;for(;k;k>>=1,a=mul(a,a))if(k&1)r=mul(a,r);return r;}
int C(int n,int m){return mul(mul(fac[n],ifac[m]),ifac[n-m]);}
int f(int,int);int g(int,int);
int f(int x,int y){return x<0||y<0? 0:dec(C(x+y,x),g(y-m-2,x+m+2));}
int g(int x,int y){return x<0||y<0? 0:dec(C(x+y,x),f(y+1,x-1));}
int main()
{
scanf("%d%d",&n,&m),fac[0]=1;
for(int i=1;i<=2*n+m+1;++i) fac[i]=mul(fac[i-1],i);
ifac[2*n+m+1]=pow(fac[2*n+m+1],P-2);
for(int i=2*n+m+1;i;--i) ifac[i-1]=mul(ifac[i],i);
printf("%d",dec(dec(C(2*n+m+1,n),f(n+m+2,n-1)),g(n-1,n+m+2)));
}