具体说来,drd 的防御战线由 nn 扇防御门组成。每扇防御门包括一个运算 opop 和一个参数 tt,其中运算一定是 OR,XOR,AND 中的一种,参数则一定为非负整数。如果还未通过防御门时攻击力为 x,则其通过这扇防御门后攻击力将变为 x op t。最终 drd 受到的伤害为对方初始攻击力 x 依次经过所有 nn 扇防御门后转变得到的攻击力。
由于 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。
2 <=n<=10 ^ 5,2<=m<=10 ^ 9
知识点:
1、与1异或,可以使特定位翻转 ,与0异或,保留其值 0101 0101 ^ 1111 0000 = 1010(翻转) 0101(保留)
2、相同的值异或为0 a^a=0
3、1除了最低位,其他位都为0,所以按位与结果取决于n最后一位,如果n最后一位是1,则结果为1,反之结果为0
4、0的二进制是: 00000000…………
-1的二进制是:1111111111…………
只用0和-1进行位运算,就可以得到任何一位的任何情况进行位运算的结果
思路:
1、目的是从(0,m)中选出一个最好的初始攻击数据K,然后通过n道门之后,数最大
2、如果把m中的每个数据都枚举一遍,肯定不可以,所以反向思考得到最佳数据
3、过程肯定是将初始值K,攻击n道门,将K转化成二进制后,其实也就是二进制数上的每一位数都攻击过n道门(位运算的性质,每个数之间相互不影响)
可以通过一个函数:
int t[N]; // t 存输入的 n 个数
string op[N]; // op 存 n 个数对应的操作
bool jisuan(bool x, int j) // jisuan 用于计算 x 经过所有数的第 j 位操作后所得到的结果
{
for (int i = 0; i < n; i ++ ) // 当前数字,遍历n道门
if (op[i] == "OR") x |= t[i] >> j & 1;
else if (op[i] == "XOR") x ^= t[i] >> j & 1;
else x &= t[i] >> j & 1;
return x;
}
4、接下来就是把所有二进制位数遍历一遍,因为本题中 m 最大是 10 ^ 9,log2(10 ^ 9) < 30,最多30位
5、当前数字一定是0 或 1,判断,谁合适当前位就填谁
bool x = jisuan(0, i), y = jisuan(1, i); // 先分别处理出该位填 0 的结果和该位填 1 的结果
if (m >> i && x < y) // 判断是否超过最大值,如果 m 右移 i 位仍不为 0,该位填 1 比该位填 0 更优,就填1
//如果填1不必0更优,那么为了让剩下能填的数更大,在该位填 0
{
sum |= y << i;
m -= 1 << i; // 填完后让 m 减去该位填 1 的结果,这样在后面填数的时候只用考虑是否大于 m 就可以了
}
else sum |= x << i; // 该位只能填 0
完整代码:
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdio>
#include <iomanip>
using namespace std;
typedef long long ull;
ull n,m,k,c,sum=0,x;
int t[100005]; // t 存输入的 n 个数
string op[100005]; // op 存 n 个数对应的操作
bool jisuan(bool x, int j) // jisuan 用于计算 x 经过所有数的第 j 位操作后所得到的结果
{
for (int i = 0; i < n; i ++ ) // 当前数字,遍历n道门
if (op[i] == "OR") x |= t[i] >> j & 1;
else if (op[i] == "XOR") x ^= t[i] >> j & 1;
else x &= t[i] >> j & 1;
return x;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=0; i<n; i++)
{
cin>>op[i]>>t[i];
}
for(int i=29; i>=0; i--)
{
bool x = jisuan(0, i), y = jisuan(1, i);
if (m >> i && x<y)
{
sum |= y << i;
m -= 1 << i;
}
else sum |= x << i;
}
cout<<sum<<endl;
return 0;
}
下面是一个更技巧的写法,真神奇
利用:
0的二进制是: 00000000…………
-1的二进制是:1111111111…………
只用0和-1进行位运算,就可以得到任何一位的任何情况进行位运算的结果
我们本来是循环一次就判断一次n道门后,0和1谁更合适
令a=0,b=-1, 把函数省掉,不用每次都判断当前这位数经过n道门之后的结果,在输入的时候直接保存了每一位数的结果
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdio>
#include <iomanip>
#include <queue>
#include <list>
#include <stack>
using namespace std;
typedef long long ull;
ull n,t,m,k,c,sum=0,x;
const int M=1<<29;
string s;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int a=0,b=-1;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>s>>x;
if(s=="AND") a&=x,b&=x;
else if(s=="OR") a|=x,b|=x;
else a^=x,b^=x;
}
k=0;
for(int i=29;i>=0;i--)
{
bool x=a>>i&1,y=b>>i&1;
if(m>>i && x<y) //填1
{
sum |= 1<<i;
m -= 1 << i;
}
else sum |= x<<i; //填0
}
cout<<sum<<endl;
return 0;
}