题解:P12420 【MX-X12-T3】「ALFR Round 5」变换
题解:P12420 【MX-X12-T3】「ALFR Round 5」变换
题目大意
给定非负整数序列
思路分析
由题意,本题的核心目标:
通过操作改变
显然地,我们可以对这两部分分别进行分析。
异或和
根据异或的定义,不难得出以下性质:
某二进制位异或和的结果由对应
再次审题,注意到条件
在
进一步的,由上述性质可知,若
从高位到低位遍历:
- 若
m 的当前位b 为1 (允许操作该位):
- 检查初始异或结果第
b 位是否为0 。- 检查是否存在
a_i 第b 位为0 (即是否存在0 可以更改,如果全是1 ,操作后a_i 的数位并不会发生改变)。- 若以上两个条件均满足,则操作该位一次,即将异或结果的
b 位翻转,答案增加2^b 。
总代价
直接给出结论:最优选择下,总代价恒等于
这个结论是显然的。根据上述性质,最后要进行异或运算时,只关注每个
那么,这道题就迎刃而解了。实现可以看代码。
代码
#include <bits/stdc++.h>
using namespace std;
// #define DEBUG 1
struct IO { // 快读快写模板
#define MAXSIZE (1 << 20)
#define isdigit(x) (x >= '0' && x <= '9')
char buf[MAXSIZE], *p1, *p2;
char pbuf[MAXSIZE], *pp;
#if DEBUG
#else
IO() : p1(buf), p2(buf), pp(pbuf) {}
~IO() { fwrite(pbuf, 1, pp - pbuf, stdout); }
#endif
inline char gc() {
#if DEBUG
return getchar();
#endif
if (p1 == p2) p2 = (p1 = buf) + fread(buf, 1, MAXSIZE, stdin);
return p1 == p2 ? ' ' : *p1++;
}
inline bool blank(char ch) {
return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
}
template <class T>
inline T read(T &x) {
double tmp = 1; bool sign = 0;
x = 0;
char ch = gc();
for(; !isdigit(ch); ch = gc())
if (ch == '-') sign = 1;
for(; isdigit(ch); ch = gc()) x = x * 10 + (ch & 15);
if(ch == '.')
for(ch = gc(); isdigit(ch); ch = gc())
tmp /= 10.0, x += tmp * (ch - '0');
if(sign) x = -x;
return x;
}
inline void read(char *s) {
char ch = gc();
for(; blank(ch); ch = gc());
for(; !blank(ch); ch = gc()) *s++ = ch;
*s = 0;
}
inline void read(char &c) { for(c = gc(); blank(c); c = gc()); }
inline void push(const char &c) {
#if DEBUG
putchar(c);
#else
if(pp - pbuf == MAXSIZE) fwrite(pbuf, 1, MAXSIZE, stdout), pp = pbuf;
*pp++ = c;
#endif
}
template <class T>
inline void write(T x) {
if (x < 0) x = -x, push('-');
static T sta[35];
T top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while(x);
while(top) push(sta[--top] + '0');
}
template <class T>
inline void write(T x, char lastChar) {
write(x), push(lastChar);
}
} io;
int a[1000010], z[32];
inline void solve() {
memset(z, 0, sizeof z); // 多测要清空
int n, m, k, tmp = 0, res;
io.read(n), io.read(m), io.read(k);
for(int i = 1; i <= n; i++)
io.read(a[i]), tmp ^= a[i];
// 统计每个位 '0' 的个数
for(int b = 0; b <= 31; b++)
for(int i = 1; i <= n; i++)
z[b] += (!(a[i] & (1 << b)));
res = tmp; // tmp 为原数组初始异或和
for(int b = 0; b <= 31; b++) {
if(!(m & (1 << b))) continue;
if(!(tmp & (1 << b)) && z[b] >= 1) // 当前位可以对答案造成贡献且存在可操作数
res += (1 << b);
}
io.write(res, '\n');
}
signed main() {
int T; io.read(T);
while(T--) solve();
return 0;
}
进一步优化
注:本部分思路由同机房巨佬 @blm_xxc 提出。
根据原代码,不难观察到最耗时的部分是分别统计
综上,可得公式: