LuoguP7637 [BalticOI 2006 Day 1] BITWISE EXPRESSIONS
题目大意
给定
我们设第
题目分析
思路:贪心。
很容易看出来,我们要想最终的结果最大,那么我们需要尽可能地满足最高位的需求,也就是优先考虑将最高位置为
这个贪心很容易就可以证明出来,因为对于一个二进制数,如果这个二进制数的第
我们为了方便操作,用
n = read(),p = read();
st[1] = 1;
for (int i = 1;i <= p;i++){
int x = read();
st[i + 1] = st[i] + x;
}
for (int i = 1;i <= n;i++){
arr[i].l = read(),arr[i].r = read();
}
做题时,我们会遇到几个问题。
-
我们如何知道一个连续的区间内存不存在一个数的二进制上的某一位上为
? -
每对数据是一个区间,如果两次取到不同的数,并且两个数的二进制完全不一样,导致了错误怎么办?
-
如果一个组内刚开始全都是一对数据在贡献,其他数据都不做贡献,会对最优解产生影响吗?
问题1
对于第一个问题,我们可以用一些巧妙的位运算将这个过程变成
对于一个连续的区间
根据刚才贪心的证明,我们很容易得到第一个大于
(l + (1ll << (bit - 1))) & -(1ll << (bit - 1)
问题2
对于第二个问题,我们可以将每对数据已经确定了的位数存入一个数组
这意味着我们可以将不包括之前确定了的二进制位的数直接舍弃吗?答案肯定是不可以。
因为这仅仅代表这对数据中的其中一个数,直接舍弃就太片面了,我们考虑若他不具备之前已经确定的二进制位,我们就强行把这些二进制位加上,看加上之后会不会超出原本数据的范围。即:
int add_bit(int x,int num){ // x 表示第一个大于 l 的符合要求的二进制数,num 表示第 num 对数据。
for (int i = 1;i <= donei[num];i++){ // donei 用于表示下标。
if (((x >> (done[num][i] - 1)) & 1) == 0){ // 如果不包括之前已经确定的二进制位。
x += (1ll << (done[num][i] - 1)); // 强行加上。
}
}
return x;
}
这样我们如果
这里有个细节,我们枚举位数时应该从大到小,不然如果从小到大的话,越到后面可能多减的也就越多。
bool in(int bit,int num){ // 用来判断第 bit 位是不是第 num 对数据必要的二进制位。
for (int i = 1;i <= donei[num];i++){ // 枚举。
if (bit == done[num][i]){ // 是必要的二进制位。
return 1;
}
}
return 0;
}
bool delcheck(int x,int l,int r,int num,int bit){
for (int i = 32;i >= 1;i--){ // 根据题目给出的数据范围,二进制下最多 32 位。
if (!in(i,num) && ((x >> (i - 1)) & 1) && i != bit){ // 如果第 i 为不是必要的二进制位,并且目前第 i 位恰好为 1,并且第 i 位也不是现在要加的二进制位。
if (x - (1ll << (i - 1)) >= l && x - (1ll << (i - 1)) <= r){ // 如果减去这一位后满足范围,返回真。
return 1;
}
else if (x - (1ll << (i - 1)) > r){ // 如果减去这一位后还是太大了,那么先减去,看后面有没有机会继续减。
x -= (1ll << (i - 1));
}
// 如果减去太小了,就不减这一位,即不作任何操作。
}
}
return 0; // 如果所有位数都枚举完了还不满足条件,返回假。
}
那么此时此刻我们的
bool isgreater(int x,int l,int r,int num,int bit){ // 判断是否大于 r
if (x > r){ // 如果大于 r
return delcheck(x,l,r,num,bit); // 进行上文所说的删位处理。
}
return 1; // 不大于r,返回真。
}
bool check(int l,int r,int bit,int num){
if ((l >> (bit - 1)) & 1){ // 如果 l 的第 bit 位上本来就是 1。
int x = add_bit(l,num); // 判断 l 的二进制包不包括之前已经确定的二进制位,没有则加上。
return isgreater(x,l,r,num,bit); // 判断是否大于 r。
}
int x = ((l + (1ll << (bit - 1))) & -(1ll << (bit - 1))); // 上文所描述的第一个大于 l 的满足条件的数。
x = add_bit(x,num); // 判断 x 的二进制包不包括之前已经确定的二进制位,没有则加上。
return isgreater(x,l,r,num,bit); // 判断是否大于 r。
}
问题3
对于第三个问题,回答是:当然会。
比如前面几位都是一对数据在贡献,这个时候出来一位,这一对数据本来是可以贡献的,但是由于前面几位数的贡献,这对数据容不下这一位了,并且组内的其他数据也无法贡献出这一位(其他数据不是因为容不下,而是区间内根本没有这一位)。但如果其他数据可以帮忙分担一下前面几位,那么就可以给其他数据留下充足的空间来存储别的数据根本没有的位数。
这个时候我们开一个数组
int ans = 0; // 统计答案
for (int bit = 32;bit >= 1;bit--){ // 枚举位数。
int cnt = 0; // 统计能够贡献出当前位的组数。
for (int idx = 1;idx <= p;idx++){ // 枚举组。
int minn = 2147483647,mini; // 查找贡献最小的。
for (int i = st[idx];i <= st[idx + 1] - 1;i++){ // 枚举组内每对数据。
if (check(arr[i].l,arr[i].r,bit,i)){ // 判断当前数据能否贡献。
if (minn >= times[i]){ // 找已经贡献的最小的数据。
minn = times[i];
mini = i;
}
}
}
if (minn != 2147483647){ // 如果有能贡献的。
cnt++; // 统计组数。
usei[cnt] = mini; // 统计这一组的贡献者。
times[mini]++; // 将这一组的贡献者的贡献次数 +1。
}
}
if (cnt == p){ // 如果贡献的组数等于总组数,说明这一位可以变成 1。
for (int i = 1;i <= p;i++){ // 将当前已经确认的位数,统计到 done 数组里面。
donei[usei[i]]++;
done[usei[i]][donei[usei[i]]] = bit;
}
ans += (1 << (bit - 1)); // 统计答案。
}
}
printf("%lld",ans);
那么这样一来这道题就已经完成了。总的代码就不放了,把上文的几段合起来就行了。
总结
这道题有一点思维难度,还要求了对于位运算的掌握。特别是对细节的处理很重要,经常忘记想到一些其他的情况而导致出错。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】