回到过去 01背包 O(∩_∩)O哈哈~

题目链接

题目描述:

想回到过去,试着让故事继续~
小y一直幻想着回到过去,改变历史。
终于,上帝给了他一次改变历史的机会。具体地说,他获得了n个时光胶囊。第i个时光胶囊可以让时光倒流aia_iai​天。我们将时光倒流天数相同的时光胶囊视为同一种。
小y想恰好回到m天前。而携带过多种类的时光胶囊会浪费太多体力。所以他想知道有哪些种类的时光胶囊是必须携带的。
数据保证一定可以选择若干个胶囊能过恰好回到m天前。
详细解释请参照样例。

输入描述:

第一行包含两个正整数n,m。含义见【题目描述】
第二行共n个用空格隔开的正整数,第i个整数ai表示一个可以使时光倒流ai​天的时光胶囊。

输出描述:

输出共两行。
第一行一个整数表示必须携带的时光胶囊的种类数。
第二行若干个整数。分别表示这些必须携带的时光胶囊所能时光倒流的天数。并且按照天数从小到大排序输出。

备注:

对于20%的数据满足:1≤n≤20
对于40%的数据满足:1≤n≤200,1≤ai,m≤103
对于另外20%的数据满足:1≤n≤200并且每种时光胶囊只有一个。
对于100%的数据满足:1≤n≤105,1≤m,ai​≤105,不同种类的时光胶囊效果之和不超过105

这是个 01背包 问题,但是需要借助二进制的机制做(用二进制的位数代替十进制数值),但 m 的数值范围太大,用整形无法表示出 2m, 所以我们需要用到一个容器(bitset)用来储存。解决了存储问题,就简单了,先把时空胶囊进行排序,把同种时空胶囊进行压缩,再对这些胶囊反向叠加并储存,然后是一个一个枚举(通过正向和反向的对比判断是否必备),最后输出就行了。代码如下:

#include<cstdio>
#include<bitset>
#include<algorithm>

using namespace std;
/************************************
不同种类之和不超过100000,
最多种类个数不超过450.
*************************************/ 
const int  N  = 1e5 + 1;

template<class T> inline void read(T &x, int xk = 10) { // quick read 
	x = 0; char ch = getchar();T f = 1;
	while(ch >  '9' || ch <  '0') {if (ch == '-') f = -1; ch = getchar();}
	while(ch <= '9' && ch >= '0') {x = x * xk + ch - '0'; ch = getchar();}
	x *= f;
}

bitset<N> pre[450], dp, res; // 用二进制位数代表十进制数 
int a[N], b[450]; //  a用来存值,b用来存数量 

int init(int n, int m) {// 压缩a,储存pre 
	int tot = 0;
	sort(a + 1, a + 1 + n);
	for(int i = 1, j; i <= n; i = j) { // 去掉相同项 
		j = i;
		a[++tot] = a[j];
		while(j <= n && a[tot] == a[j]) {
			b[tot] ++; // 记录相同项个数 
			j ++;
		}
	}
	pre[tot][0] = 1;
	for(int i = tot; i >= 2; --i) {// 反向叠加 
		pre[i-1] = pre[i];
//		for(int j = 1; j <= b[i]; ++j) pre[i] |=  pre[i] << a[i];
//      结构优化,减少重复操作,提高效率 
		int T = b[i], t = 1;
		while(T && t * a[i] <= m) {
			pre[i-1] |= pre[i-1] << (t * a[i]);
			T-=t; t = (T <= t<<1) ? T : (t<<1);
		} 
	}
	return tot; 
}

int main() {
	int n, m, tot;
	read(n); read(m);
	for(int i = 1; i <= n; ++i) read(a[i]);
	tot = init(n, m);// 初始化 
	dp[0] = 1; 
	for(int i = 1; i <= tot; ++i) { // 枚举 
		res[i] = 1;
		for(int j = 0; j <= m; ++j) {
			if (dp[j] & pre[i][m-j]) {
				res[i] = 0; // 不必备 
				break; 
			}
		}
//		for(int j = 1; j <= b[i]; ++j) ans |= ans <<  a[i];
//		正向叠加 同样进行结构优化 
		int T = b[i], t = 1;
		while(T && t * a[i] <= m && i != tot) {
			dp  |=  dp << ( t * a[i] );
			T-=t; t=(T<=t<<1)?T:(t<<1);
		} 
	}
	printf("%d\n", res.count());
	for(int i = 1; i <= tot; ++i) {
		if (res[i]) printf("%d ", a[i]);
	}
	return 0;
}
发布了8 篇原创文章 · 获赞 5 · 访问量 643

猜你喜欢

转载自blog.csdn.net/Long_hen/article/details/105339844
o