题目链接
题目描述:
想回到过去,试着让故事继续~
小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;
}