题目描述
难度分:2400
输入T(≤103)表示T组数据。每组数据输入n(1≤n≤1018)。
这是一道交互题。你和评测机玩游戏,轮流操作n。
每回合,当前玩家把n分解为p1和p2,满足0<p1<n且0<p2<n且p1⊕p2=n。然后对手选择p1或者p2,替换掉n。下一回合轮到对手操作。无法把 n 分解的玩家输掉游戏。
你可以决定扮演先手(打印first
表示先操作n)还是后手(打印second
表示让评测机先操作n)。
你需要保证自己获胜。轮到你操作的回合数必须≤63。
具体的交互格式见原题。
输入样例
4
1
0 0
3
0 0
13
3 4
0 0
777777770001
0 0
输出样例
second
first
2 1
first
10 7
1 2
first
777777770000 1
算法
博弈论+构造
假设popcount(x)表示求x的二进制中1的个数,当popcount(n)=1时n是无法被拆分的,先手必败。popcount(n)=2时n可以拆分为popcount(p1)=popcount(p2)=1,先手必胜。
因此猜测,popcount(n)为偶数时先手必胜。可以发现:
- popcount(n)为奇数时,选择后手。先手拆出来的两个数p1和p2一定满足popcount(p1)和popcount(p2)一奇一偶,此时我们选择将n变成popcount值为偶数的那个数,这样我们就又回到了必胜态。
- popcount(n)为偶数时,选择先手。先手拆出来的两个数p1和p2一定满足popcount(p1)和popcount(p2)奇偶性相同,那么我们选择将n拆成两个popcount值为奇数的两个数,把popcount值为奇数的情况扔给了对手,对手仍然处于必败态。
我们可以每次将n拆成higbit(n)和n⊕highbit(n)(其中highbit(x)表示获得x的最高位),这样每拆分一次就会使得n少一位,从而使游戏控制在O(log2n)步≤63。
注意highbit操作不能替换成lowbit操作。用lowbit来拆的操作次数可以达到O(n)级别。具体的,对于m位的二进制数把后 2位分离,每次后手给前m−2位做−1操作,并用后两位平衡1的数量奇偶性。先手每次只能拆最后两位,无法影响前面m−2位,操作数量是O(n)级别的。
复杂度分析
时间复杂度
highbit和popcount操作都是O(1)的,拆分过程每次至少会去掉一位,因此最多进行O(log2n)次操作就能结束游戏。因此,整个算法的时间复杂度为O(log2n)。
空间复杂度
整个算法过程中仅使用了有限几个变量,额外空间复杂度为O(1)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int T;
LL n;
void solve(LL n) {
function<bool(LL)> get = [&](LL x) {
return __builtin_popcountll(x) & 1;
};
function<LL(LL)> highbit = [&](LL x) {
return 1ll << (63 - __builtin_clzll(x));
};
puts(get(n)? "second": "first");
fflush(stdout);
if(!get(n)) {
// 先手
printf("%lld %lld\n", highbit(n), n^highbit(n));
}
fflush(stdout);
while(1) {
LL p1, p2;
scanf("%lld%lld", &p1, &p2);
if(p1 == -1) assert(0);
if(p1 == 0 && p2 == 0) break; // 对手分解不了,游戏结束
if(get(p1)) {
// 让n变为p2
printf("%lld %lld\n", highbit(p2), p2^highbit(p2));
}else {
// 让n变为p1
printf("%lld %lld\n", highbit(p1), p1^highbit(p1));
}
fflush(stdout);
}
}
int main() {
scanf("%d", &T);
while(T--) {
scanf("%lld", &n);
solve(n);
}
return 0;
}