01 背包冲过4e8??不如学学 bitset
chenxi2009 · · 题解
前言
我质疑题解区大部分 01 背包解法的复杂度正确性。01 背包做法时间复杂度为
于是,我在此提出一种思路、代码均比 01 背包简单且思路相似的做法,最坏点仅用时 9ms。撰写此文章时在最优解第一页,此外还是疑似最短解法。提交记录
思路
我们要解决的第一个问题是“让同学不抗议”。这意味着每对“实力相当的同学”要么同时被选,要么同时不选。如果把“实力相当”的关系看作无向边,同学看作结点,那么我们将得到一个无向图,其中处在同一个连通块内的同学要么同时被选,要么同时不选。
此处我们只关心同学之间的连通关系,故可以用并查集维护。根据题意,接下来我们要选中若干个连通块,使得它们的大小之和与
我们不妨把连通块的大小列为数组
我们令
如果我们新加入一个大小为
诶那这不是和 01 背包一样坏吗?不要着急,让我们引入 bitset。我们注意到如果我们把
我们知道对一个整数做位运算可以看作常数时间,但是这个整数
综上,用 bitset 实现的 DP 时间复杂度为
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 20001;
int n,m,k,f[N],a,b,sz[N],ans;
int find(int x){return x == f[x] ? x : f[x] = find(f[x]);}
bitset<N> g;
int main(){
cin.tie(0)->sync_with_stdio(0);
cin >> n >> m >> k;
for(int i = 1;i <= n;i ++) f[i] = i,sz[i] = 1;
for(int i = 1;i <= k;i ++){
cin >> a >> b;
a = find(a),b = find(b);
if(a != b) sz[a] += sz[b],f[b] = a;//合并两个实力相当的同学所在的并查集
}
g[0] = 1;//初始化边界条件
for(int i = 1;i <= n;i ++) if(i == find(i)) g |= g << sz[i];//DP,对于每一个并查集做一次位运算
for(int i = 1;i <= n;i ++) if(g[i] && abs(i - m) < abs(ans - m)) ans = i;//找到和 m 最接近的可达的和 i
printf("%d\n",ans);
return 0;
}
其他解法——对于 01 背包做法的进一步优化
我们可以把相同大小的连通块合并为一类物品,做多重背包。
由于连通块大小之和为