Wannafly挑战赛26-F-msc的棋盘[最小割转化dp]

题意

一个大小为 \(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;
}

猜你喜欢

转载自www.cnblogs.com/yqgAKIOI/p/9833346.html