我也得了起床综合困难症…啊啊啊
好了,停,还是直接开始吧
先放题目链接:
https://www.luogu.com.cn/problem/P2114
题目描述
21世纪,许多人得了一种奇怪的病:起床困难综合症,其临床表现为:起床难,起床后精神不佳。作为一名青春阳光好少年,atm一直坚持与起床困难综合症作斗争。通过研究相关文献,他找到了该病的发病原因: 在深邃的太平洋海底中,出现了一条名为drd的巨龙,它掌握着睡眠之精髓,能随意延长大家的睡眠时间。 正是由于drd的活动,起床困难综合症愈演愈烈, 以惊人的速度在世界上传播。为了彻底消灭这种病,atm决定前往海底,消灭这条恶龙。历经千辛万苦,atm终于来到了drd所在的地方,准备与其展开艰苦卓绝的战斗。drd有着十分特殊的技能,他的防御战线能够使用一定的运算来改变他受到的伤害。具体说来,drd的防御战线由n扇防御门组成。每扇防御门包括一个运算op和一个参数t,其中运算一定是OR,XOR,AND中的一种,参数则一定为非负整数。如果还未通过防御门时攻击力为x,则其通过这扇防御门后攻击力将变为x op t。最终drd受到的伤害为对方初始攻击力x依次经过所有n扇防御门后转变得到的攻击力。
由于atm水平有限,他的初始攻击力只能为0到m之间的一个整数(即他的初始攻击力只能在 0, 1, … , m中任选,但在通过防御门之后的攻击力不受m的限制)。为了节省体力,他希望通过选择合适的初始攻击力使得他的攻击能让drd受到最大的伤害,请你帮他计算一下,他的一次攻击最多能使drd受到多少伤害。
输入输出格式
输入格式:
输入文件的第 1 行包含 2 个整数,依次为n, m,表示 drd 有n扇防御门,atm 的初始攻击力为0到m之间的整数。
接下来n行,依次表示每一扇防御门。每行包括一个字符串op和一个非负整数t,两者由一个空格隔开,且op在前,t在后,op表示该防御门所对应的操作,t表示对应的参数。
输出格式:
输出一行一个整数,表示atm的一次攻击最多使drd受到多少伤害。
输入输出样例
输入样例#1:
3 10
AND 5
OR 6
XOR 7
输出样例#1:
1
说明
【样例说明】
atm可以选择的初始攻击力为 0,1, … ,10。
假设初始攻击力为 4,最终攻击力经过了如下计算
4 AND 5 = 4
4 OR 6 = 6
6 XOR 7 = 1
类似的,我们可以计算出初始攻击力为 1,3,5,7,9 时最终攻击力为 0,初始攻击力为 0,2,4,6,8,10 时最终攻击力为 1,因此atm的一次攻击最多使drd受到的伤害值为1。
【数据规模与约定】
初步思路
暴力解决,枚举0~m,大家都挺喜欢超时的哈哈哈
思考:位运算的特征
我们知道,在计算机里数字按二进制存储,也就是一个数的每一位不是0就是1,当0、1进行相同次序的位运算以后得出来的也是0、1。
m<=10^9,即m的位数不超过30位。
我们需要最后进行位运算以后的答案最大,也就是说,从最高位开始到最低位,我们要让每一位尽可能为1。
例如,我们有3位,从第三位开始,我们让第三位为1,即100,值为4,如果第三位为0,值为0;
第二位为1,得到110,值为6,010值为2,000值为0;
总结一下就是,当一个数的第i位经过一系列位运算为1时,对答案的贡献就是1<<i;
当一个数的第i位经过一系列位运算为0时,对答案没有贡献。
那么,我们考虑当一个数的第i位经过一系列位运算为1时的情况:
情况1.第i位数原来是0,经过一系列位运算变成1;
情况2.第i位数原来是1,经过一系列位运算变成1;
对于情况1:如果第i位原来是0,后来变成了1,0~m的二进制中,可以任意实现第i位是0,所以答案直接计算加上贡献值1<<i;
对于情况2:第i位数原来是1,经过一系列位运算变成1,然而,对于这种情况,必须是i小于m是1的最高位,因为大于m是1的最高位就超出了m的范围;
例如5,转换为二进制是101,那么i<=3;
转换一下,也就是说,这个条件是:(1>>i)的值小于m;
这里要注意,计算完以后要让m的这一位变成0,即101,变成001,不然当i=2时,也小于m=5,判断错误。
那么我们只需要对30位全是0,和30位全是1,即a0=0,二进制为000000…,a1=-1,二进制为11111…,来进行一系列的位运算。
然后根据位运算后是1的情况来进行对最后答案的计算。
代码如下:
#include<bits/stdc++.h>
using namespace std;
int a1=0,a2=-1;
int ans=0;
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;++i)
{
char str[5];
scanf("%s",str);
int x; cin>>x;
if(str[0]=='A') a1&=x,a2&=x;
else if(str[0]=='O') a1|=x,a2|=x;
else if(str[0]=='X') a1^=x,a2^=x;
}
for(int j=29;j>=0;--j)
{
if((a1>>j)&1) ans+=(1<<j);//第i位原来是0
else if((a2>>j)&1&&(1<<j)<m) ans+=(1<<j),m-=(1<<j);//第i位原来是1
}
printf("%d\n",ans);
return 0;
}
这次的位运算学习,就到今天结束了。
这一次复盘位运算又学到了很多东西,位运算真的是十分巧妙有趣的东西,还需要通过反复实践琢磨。
明天开始第二部分的递推与递归,希望打败了巨龙的我不再起床困难了。
最后,放一个小总结:
1.位运算的关键是二进制0、1的问题
2.0、1可以用来表示多种情况,选与不选
3.利用二进制+位运算可以有效地降低时间复杂度。
下次位运算,再会啦。