CF1913C Game with Multiset 题解
【题目描述】
你有一个空的多重集,你需要处理若干下列询问:
- ADD $ x $:加入一个数值为 $ 2^x $ 的元素到该多重集。
- GET $ w $:判断是否存在一个该多重集的子集,使得这个子集的所有元素之和等于 $ w $。
$ 1≤m≤105,0≤x≤29,0≤w≤109 $
【思路】
操作一:ADD
首先,这个多重集里面只有二的幂次方的数,所以我们可以定义一个 $ sum $ 数组来表示这个多重集,$ sum_i $ 表示这个多重集里面有多少个数值为 $ 2^i $ 的元素。
这样子,操作一就很好实现了,直接 $ sum_x+1 $ 就行。
操作二:GET
我们发现,如果要让这个多重集有一个子集加起来等于 $ w $,我们需要满足:
- 对于 $ w $ 的二进制表示法,如果倒数第 $ i $ 位为 $ 1 $(最后一位算第 $ 0 $ 位),则多重集之中一定存在一个 $ 2^{i} $。
但是现在我们的多重集可能会有多个相同的元素,他们可以“进位”成一个新元素,就像两个 $ 2^x $ 可以合并为一个 $ 2^{x+1} $ 一样。
这就难办了,我们不但需要考虑 $ w $ 需不需要当前的 $ 2^x $,还要关心“进位”。
我们可以这么想:
- 把这个多重集复制到一个新数组 $ sum1 $ 里去(以防后面的操作改变这个多重集原本结构)。
- 从低到高扫 $ w $ 的二进制位。
- 如果 $ w $ 倒数第 $ i $ 位为 $ 1 $(最后一位算第 $ 0 $ 位),则它需要一个值为 $ 2^{i} $ 的元素,$ sum1_i-1 $。
- 把其他所有当前值的元素进位(反正已经没用了)。
如果在取 $ w $ 的某一个二进制位置,发现没有这个值的元素了,就说明拼不出来。
否则就可以通过选取和“进位”拼出来。
【Code】
#include <bits/stdc++.h>
using namespace std;
int sum[35],sum1[35];
int m,op,x,w;
bool Output;
int main()
{
scanf("%d",&m);
for(int i=1;i<=m;i++){
scanf("%d",&op);
if(op==1){
scanf("%d",&x);
sum[x]++;
}else{
scanf("%d",&w);
Output=false;
for(int i=0;i<=31;i++) sum1[i]=sum[i]; //1.分离临时数组
for(int i=0;i<=31;i++){ //2.从小到大枚举二进制位
int flag=w&(1<<i); //3.检查当前位是否需要元素
if(flag){
if(sum1[i]==0){ //需要,但是没有
puts("NO");
Output=true;
break;
}else{ //否则就用掉
sum1[i]--;
}
}
sum1[i+1]+=sum1[i]/2; //4.“进位”
}
if(!Output) puts("YES");
}
}
return 0;
}