msc的棋盘 - 网络流 - 最小割 - dp

题目大意:
有一张网格图,每个位置有至多 1 1 1个棋子。告诉你每一列有多少棋子 { b m } \{b_m\} {bm},问有多少行的情况 { a n } \{a_n\} {an}(即每一行有多少棋子),使得存在一种情况,满足这两个限制。
例如当 n = 4 , m = 2 , b 1 = 1 , b 2 = 3 n=4,m=2,b_1=1,b_2=3 n=4,m=2,b1=1,b2=3的时候, { 1 , 1 , 1 , 1 } , { 0 , 1 , 1 , 2 } \{1,1,1,1\},\{0,1,1,2\} {1,1,1,1},{0,1,1,2}及他们的多重排列共13中情况都是合法的( { 0 , 1 , 1 , 2 } \{0,1,1,2\} {0,1,1,2} { 1 , 2 , 0 , 1 } \{1,2,0,1\} {1,2,0,1}被认为是两种方案)。 n , m ≤ 50 , b i ≤ n n,m\le50,b_i\le n n,m50,bin
题解:
考虑告诉你 { a n } \{a_n\} {an}怎么判定,其实这是另一个经典的问题,方法是先考虑朴素网络流,这个显然,然后转化为最小割,那么显然结果是,如果左边(行)选 x x x个,一定会选前 x x x小的,右边 y y y同理,记 s a [ x ] sa[x] sa[x]表示左边 a a a的前 x x x小,那么最小割是: min ⁡ 1 ≤ x ≤ n , 1 ≤ y ≤ m   c o s t ( x , y ) = s a [ x ] + s b [ y ] + ( n − x ) ( m − y ) \min_{1\le x\le n,1\le y\le m}\ cost(x,y)=sa[x]+sb[y]+(n-x)(m-y) 1xn,1ymmin cost(x,y)=sa[x]+sb[y]+(nx)(my)
(如果就是这样一个判定可行的题目的话,你可以用类似斜率优化和桶排做到线性判定)
现在我们希望最大流是 s = ∑ i = 1 m b i s=\sum_{i=1}^m b_i s=i=1mbi,也就是最小割是这个东西,那么就需要(显然一定存在一种割法使得代价是 s s s): ∀ x , y , c o s t ( x , y ) ≥ s \forall x,y,cost(x,y)\geq s x,y,cost(x,y)s
这样对于每个 x x x,我们就可以求出一个 s a [ x ] sa[x] sa[x]的下界,不妨记作 L [ x ] L[x] L[x]
然后我们就得到了能够使得 { a n } \{a_n\} {an}有解的充要条件。
然后就可以 d p \mathrm{dp} dp了,我们设 d p ( i , j , k ) dp(i,j,k) dp(i,j,k)表示填了 j j j行,最大值是 i i i,和是 k k k,那么枚举 t t t表示填 t t t i + 1 i+1 i+1,并在剩下的 n − j n-j nj行中选择 t t t行:
d p ( i + 1 , j + t , k + t ( i + 1 ) ) + = d p ( i , j , k ) ( n − j t ) , ∀ p ∈ [ 0 , t ] , k + p ( i + 1 ) ≥ L [ k + p ] dp(i+1,j+t,k+t(i+1))+=dp(i,j,k)\binom{n-j}{t},\\ \forall p\in[0,t],k+p(i+1)\geq L[k+p] dp(i+1,j+t,k+t(i+1))+=dp(i,j,k)(tnj),p[0,t],k+p(i+1)L[k+p]
注意转移的时候要满足第二行,不满足置 0 0 0(不合法)。
最后 d p ( m , n , s ) dp(m,n,s) dp(m,n,s)就是答案。

#include<bits/stdc++.h>
#define gc getchar()
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define lint long long
#define mod 1000000007
using namespace std;const int N=55;int b[N],L[N],dp[N][N][N*N],C[N][N];
int main()
{
	int n,m;scanf("%d%d",&n,&m);rep(i,1,m) scanf("%d",&b[i]);sort(b+1,b+m+1);rep(i,1,m) b[i]+=b[i-1];
	rep(x,1,n) rep(y,0,m) L[x]=max(L[x],b[m]-b[y]-(n-x)*(m-y));int s=b[m];
	rep(i,0,n) C[i][0]=1;rep(i,1,n) rep(j,1,i) C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
	rep(i,0,n&&!L[i]) dp[0][i][0]=C[n][i];
	rep(i,0,m-1) rep(j,0,n) rep(k,L[j],s-(n-j)*(i+1)) if(dp[i][j][k])
		rep(t,0,n-j&&k+(i+1)*t<=s&&k+(i+1)*t>=L[j+t]) (dp[i+1][j+t][k+(i+1)*t]+=(lint)dp[i][j][k]*C[n-j][t]%mod)%=mod;
	return !printf("%d\n",dp[m][n][s]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值