n×m的棋盘,放棋子,要求第i行左边 l i l_i li个格子和右边 r i r_i ri个格子各恰好有一个棋子,且每一列最多只能有1个棋子,问方案数,对 1 0 9 + 7 10^9+7 109+7取模
n ≤ 50 , m ≤ 200 n\leq50,m\leq200 n≤50,m≤200
这是一道DP好题。
太菜了以至于我想了半天连状态都没想到。
考虑到每一列最多放一个,那么考虑在列上做文章,设 f [ i ] [ ] f[i][] f[i][]表示做到了第i列……
然后怎么办呢,对于一列,假如一些行左区间到此为止了,那么就一定要将这些行放完,将这些行安排之前没有被放过的列;否则先把它们咕咕了。如果一些行的右区间从这一列开始,那么没有处理完的行就会多出几个。其次你也可以选择填在中间没有覆盖的地方。
如果这么考虑的话,状态就很好想了(可是就是没有这么考虑)
设 f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示做到了第i列,在之前有j列没有被放过棋子,有k行已经到了右区间。
记 l i , r i , m i d i l_i,r_i,mid_i li,ri,midi分别为左区间右端点为i、右区间左端点为i、i列没有区间覆盖住的行数
每一列的转移要添加 r i + 1 r_{i+1} ri+1的右区间放置机会,要将 l [ i + 1 ] l[i+1] l[i+1]全部搞定。
初状态为 f [ 0 ] [ 0 ] [ 0 ] = 1 f[0][0][0]=1 f[0][0][0]=1
考虑三种转移:
#include
#include
#include
#include
#define fo(i,a,b) for(int i(a),_E_(b);i<=_E_;++i)
#define REP(i,a,b) for(int i(a),_E_(b);i<_E_;++i)
#define fd(i,a,b) for(int i(a),_E_(b);i>=_E_;--i)
#define ll long long
#define mo 1000000007
#define N 52
#define M 210
using namespace std;
int f[M][M][N],l[M],r[M],mid[M],n,m,p[M][M];
int main(){
scanf("%d %d",&n,&m);
fo(i,0,m){
p[i][0]=1;fo(j,1,i)p[i][j]=1ll*p[i][j-1]*(i-j+1)%mo;
}
fo(i,1,n){
int L,R;scanf("%d %d",&L,&R);
++l[L];++r[m-R+1];++mid[L+1];--mid[m-R+1];
}
fo(i,2,m)mid[i]+=mid[i-1];
f[0][0][0]=1;
REP(i,0,m)fo(j,0,i)fo(k,0,n)if(f[i][j][k]){
if(j+1>=l[i+1])(f[i+1][j+1-l[i+1]][k+r[i+1]]+=1ll*p[j+1][l[i+1]]*f[i][j][k]%mo)%=mo;
if(j>=l[i+1]&&k+r[i+1])(f[i+1][j-l[i+1]][k-1+r[i+1]]+=1ll*p[j][l[i+1]]*(k+r[i+1])%mo*f[i][j][k]%mo)%=mo;
if(j>=l[i+1])(f[i+1][j-l[i+1]][k+r[i+1]]+=1ll*p[j][l[i+1]]*mid[i+1]%mo*f[i][j][k]%mo)%=mo;
}int ans=0;fo(i,0,m)ans=(ans+f[m][i][0])%mo;
printf("%d",ans);
return 0;
}