题目描述
(懒得写了,见原题)
Sol
一个很神仙的组合问题
稍微模拟就可以发现一个事实,当上一行选择了删去
x
x
x,后下一行可选的数是
[
x
−
1
,
m
]
[x-1,m]
[x−1,m]
所以dp也就是一个不断求前缀和并加上后一个的过程:
f
[
i
]
[
j
]
=
∑
k
=
0
j
+
1
f
[
i
−
1
]
[
k
]
f[i][j]=\sum_{k=0}^{j+1}f[i-1][k]
f[i][j]=∑k=0j+1f[i−1][k]
之后再往这方面想就比较难有收获了,我们转换一下模型,这个显然类似一个格路问题,只不过允许在某个地方至多一次向下走一格,这样的东西就很不好求。
所以我们必须要把这样的东西转换一下 (无图,以下纯属瞎BB) ,我们画出一个dp的转移DAG图,容易发现其中有一些斜着的边,那么我们把斜着的边给扯直了,这样所有边都在网格上,发现这时我们不能经过两条直线
于是问题转化为求
(
0
,
0
)
(0,0)
(0,0)到
(
n
+
m
,
n
)
(n+m,n)
(n+m,n)的格路数并且不经过
y
=
x
+
2
y=x+2
y=x+2和
y
=
x
−
m
−
1
y=x-m-1
y=x−m−1两条直线
然后就很神仙了,容斥计算
答案=总方案
−
-
− 第一个经过了
y
=
x
+
2
y=x+2
y=x+2的
−
-
− 第一个经过了
y
=
x
−
m
−
1
y=x-m-1
y=x−m−1的
考虑怎么求出经过一条直线的方案,常用思想把终点关于该直线对称,这样每一条到这个对称后的终点的路径都对应了一条不合法路径
但是说起来简单实际写起来却比较难理解,每次需要递归翻折才能求出以某个直线开头的方案(其实我也不会啊 TAT , 哪位大佬会的教教我为什么要这么做)
代码:
#include<bits/stdc++.h>
using namespace std;
#define Set(a,b) memset(a,b,sizeof(a))
const int mod=1e9+7;
int n,m;
typedef long long ll;
template<class T>inline void Inc(T&x,int y){x+=y;if(x>=mod) x-=mod;return;}
template<class T>inline void Dec(T&x,int y){x-=y;if(x < 0) x+=mod;return;}
template<class T>inline int fpow(int x,T k){int ret=1;for(;k;k>>=1,x=(ll)x*x%mod)if(k&1) ret=(ll)ret*x%mod;return ret;}
namespace Sol{
const int N=3e6+1;
int fac[N],finv[N];
inline int C(int n,int m){return n<m? 0:(ll)fac[n]*finv[m]%mod*finv[n-m]%mod;}
inline void Sieve(){
fac[0]=finv[0]=1;
for(int i=1;i<N;++i) fac[i]=(ll)fac[i-1]*i%mod;
finv[N-1]=fpow(fac[N-1],mod-2);
for(int i=N-2;i;--i) finv[i]=(ll)finv[i+1]*(i+1)%mod;
return;
}
inline void Flip(ll&x,ll&y,ll c){ll dt=x+c-y;x=x-dt;y=y+dt;return;}
void work(){
Sieve();
int ans=C(m+(n<<1),n);
ll x,y,op;
x=n+m,y=n;op=-1;
while(233) {
op==1? Flip(x,y,2):Flip(x,y,-1-m);
if(x<0||y<0) break;
op==1? Inc(ans,C(x+y,x)):Dec(ans,C(x+y,x));
op=-op;
}
x=n+m,y=n,op=-1;
while(233){
op==1? Flip(x,y,-1-m):Flip(x,y,2);
if(x<0||y<0) break;
op==1? Inc(ans,C(x+y,x)):Dec(ans,C(x+y,x));
op=-op;
}
cout<<ans<<endl;
}
}
int main()
{
cin>>n>>m;
Sol::work();
return 0;
}