题意
一个大小为 \(n*m\) 的棋盘,知道每一列放了多少棋子,求有多少摆放方案满足要求。
\(n,m\leq 50\) .
分析
如果是求是否有方案的话可以考虑网络流,行列连边,列容量为 \(b_j\),行容量为 \(m\) 。
考虑转化成一个最小割问题,假设\(S\rightarrow row\) 有 \(i\) 条边,\(column \rightarrow 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\) 行的方案数为 \(\binom{n-j}{x}\) ,要保证对于 \(p\in [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\n",f[m+1][n][sum]);
return 0;
}