题意
一个大小为 (n*m) 的棋盘,知道每一列放了多少棋子,求有多少摆放方案满足要求。
(n,mleq 50) .
分析
-
如果是求是否有方案的话可以考虑网络流,行列连边,列容量为 (b_j),行容量为 (m) 。
-
考虑转化成一个最小割问题,假设(S ightarrow row) 有 (i) 条边,(column ightarrow T) 有 (j) 条边,中间显然要断开 ((n-i)*(m-j))条边。在这样的情况下左边和右边的边一定是前 (i) 小和前 (j) 小的边。
-
发现只要最后 (sum{a_i}=sum{b_i}) ,且对于排序后的 (a) 任意的 (i) 满足(s_i=sum_{k=1}^{i}{a_i}) 都比其下限大就一定是合法方案。
-
下限 (low):(s_i) 在所有的 (j) 的情况下的最大值。如果小于下限就会出现比 (sum{b_i}) 还要小的割,不符合题意。
-
考虑定义状态 (f_{i,j,k}) 表示权值不超过 (i),选了 (j) 行,和为 (j) 的方案数。
-
转移枚举 (i) 选择了 (x) 个,然后在剩下的行中选择 (x) 行的方案数为 (inom{n-j}{x}) ,要保证对于 (pin [0,x]),有 ((k+p*x)geq {low}_{j+p})
-
总时间复杂度为 (O(n^3m^2))。
代码
#include<bits/stdc++.h>
using namespace std;
#define go(u) for(int i=head[u],v=e[i].to;i;i=e[i].last,v=e[i].to)
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define pb push_back
typedef long long LL;
inline int gi(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-48;ch=getchar();}
return x*f;
}
template<typename T>inline bool Max(T &a,T b){return a<b?a=b,1:0;}
template<typename T>inline bool Min(T &a,T b){return b<a?a=b,1:0;}
const int N=54,inf=0x3f3f3f3f,mod=1e9 + 7;
int n,m,sum;
int b[N],a[N],c[N][N],f[N][N][N*N];
void add(int &a,int b){a+=b;if(a>=mod) a-=mod;}
int main(){
n=gi(),m=gi();
rep(i,1,m) b[i]=gi(),sum+=b[i];
sort(b+1,b+1+m);
rep(i,1,m) b[i]+=b[i-1];
rep(i,1,n){
int tmp=inf;
rep(j,1,m) Min(tmp,(n-i)*(m-j)+b[j]);
a[i]=sum-tmp;
}
rep(i,0,n){
c[i][0]=1;
rep(j,1,i) c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
}
f[0][0][0]=1;
rep(i,0,m)
rep(j,0,n)
rep(k,0,sum){
for(int v=0;j+v<=n&&k+i*v<=sum;++v){
if(a[j+v]>k+i*v) break;
add(f[i+1][j+v][k+i*v],1ll*f[i][j][k]*c[n-j][v]%mod);
}
}
printf("%d
",f[m+1][n][sum]);
return 0;
}