【题解】P9339 霍尔定理 性质 dp优化
upd:2023/9/20 补充了基于霍尔婚配定理的证明。
首先观察前面两档部分分,很显然——第一档直接背包,第二档给我们一个思想就是基于桶去找饼干,直接贪心地选数量尽可能多的饼干种类就行,正确性显然。
也就是说我们找到一组确定的尽可能少的桶后直接贪心地选择饼干就行了。
接下来问题是 如何找到一组合法的桶。
最大的桶 需要被所有不同的饼干装满,需要 至少有 种不同的饼干。
次大的桶 需要被所有不同的饼干装满,需要在装满 后至少有 种不同的饼干,而有两块或更多的饼干可以同时用于装两个桶。
再次大的桶 需要被所有不同的饼干装满,需要在装满 后至少有 种不同的饼干,而有两块的饼干可以同时用于装两个桶,有三块或更多的饼干可以拿来同时装三个桶。
……
这个条件显然是必要的。其充分性可以归纳证明,也可以使用霍尔定理证明:
构造一个二分图,每个左部点是桶,右边是饼干,每个左部点向右边连一条容量为 的边,使用霍尔定理,对于集合 能匹配 ,右侧的 每个就可以匹配不超过 个,所以 的大小只和 相关,所以对于每个 ,选择前 大的 作为 中的左部点,那么就有了上面的式子。
取上界 。
现在就变成了,从大到小选 尽可能少的桶,使得前 大的和不超过 ,且和为 。
首先有 的 dp,让 表示前 大的桶,目前选了 个,和为 是否可行。
转移就枚举这个桶是否选,个数从小到大做完全背包即可。
考虑优化,因为是前 大的桶,所以 ,故有 ,所以第二维只用开到 ,复杂度是调和级数 。
注意到 dp 值是 boolean 的,所以可以压位,时空均为 ,空间约 400M 可以通过。
代码
/*
君は瞬きと共に過ぎてく時間も
遠くから見てると微笑んで
*/
/*author::ღꦿ࿐(DeepSea.) */
const int N = 15002 ;
int n , a[N + 10] , b[N + 10] , m;
int cir[N + 10] ;
int val[N + 10] ;
int cnt[N + 10] , sum[N + 10] ;
using state = bitset<N + 2>;
vector < state > dp[N + 2] ;
bitset<N + 2> px[N + 2];
vi C;
void go(int i,int j,int s) {
if(i == 1) {
C.emplace_back(j) ;
return ;
}
for(int pre = 1 ; pre <= m && b[pre] * (i - 1) <= N ; ++ pre) {
if(b[pre] >= j && dp[i - 1][b[pre]][s - j]) {go(i - 1,b[pre],s - j) ; C.emplace_back(j); return ;}
}
}
signed main()
{
read(n) ;
rep(i,1,n) read(a[i]) , cnt[a[i] + 1] ++ , sum[a[i] + 1] += a[i];
read(m);
rep(i,1,m) read(b[i]);
int c = n , s = 0;
rep(i,1,N) {
c -= cnt[i] ;
s += sum[i] ;
cir[i] = c * i + s ;
}
rep(i,1,N) dp[i].resize(N / i + 2);
rep(i,1,m) {
if(b[i] <= cir[1]) dp[1][b[i]][b[i]] = 1 ;
}
px[0][0] = 1;
rep(i,1,N) px[i] = px[i - 1] << 1 , px[i][0] = 1 ;
int ptr = m ;
rep(i,1,N - 1) {
state s ;
while(i * b[ptr] > N) ptr -- ;
for(int j = ptr ; j >= 1 ; -- j) {
s |= dp[i][b[j]] ;
if((i + 1) * b[j] <= N) {
dp[i + 1][b[j]] |= s << b[j] ;
dp[i + 1][b[j]] &= px[cir[i + 1]];
}
}
}
rep(i,1,N) {
for(int j = 1 ; j <= m && b[j] * i <= N ; ++ j) {
if(dp[i][b[j]][s]) {
go(i,b[j],s);
goto Construct;
}
}
}
puts("-1") ;
exit(0) ;
Construct:{
priority_queue< pi > h ;
vector<vi> ans;
rep(i,1,n) h.emplace(a[i] , i);
for(int p:C) {
vector< pi > tmp ;
vi ths;
while(p -- ) {
auto [c,num] = h.top( ) ;h.pop( );
if(c > 1) tmp.emplace_back(c - 1 , num);
ths.emplace_back(num) ;
}
ans.emplace_back(ths);
for(auto x:tmp) h.push(x);
}
wrt( C.size( ) , '\n');
for(auto vec:ans) {
wrt(vec.size( ) , ' ');
wrt(vec, ' ') ;
nxtl;
}
exit(0) ;
}
return 0;
}
本文已经结束了。本文作者:ღꦿ࿐(DeepSea),转载请注明原文链接:https://www.cnblogs.com/Dreamerkk/p/17970926,谢谢你的阅读或转载!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步