The 2020 ICPC Asia Macau Regional Contest G. Game on Sequence(博弈/好题)
Grammy is playing a game with her roommate Alice on a sequence 𝐴A with 𝑛n non-negative integers 𝐴1,𝐴2,…,𝐴𝑛A1,A2,…,An. The rules of the game are described as follows.
- They play the game by moving the single token on the sequence, initially the token is at position 𝑘k.
- Grammy takes the first move, and they take moves alternatively.
- In any move with the token at position 𝑖i, the current player must move the token to the next position 𝑗j such that 𝑗>𝑖j>i and 𝐴𝑗Aj differs from 𝐴𝑖Ai on at most one bit in binary representation.
- The player who can't make any legal move loses the game.
They play this game many times and the sequence can be modified many times. Grammy wants to ask you for some initial states who will win the game if both play optimally.
Input
The first line of input contains 2 integers 𝑛n and 𝑚m (1≤𝑛,𝑚≤2000001≤n,m≤200000), denoting the length of the sequence and the number of operations.
The second line contains 𝑛n integers 𝐴1,𝐴2,…,𝐴𝑛A1,A2,…,An (0≤𝐴𝑖≤2550≤Ai≤255), denoting the sequence 𝐴A.
The next 𝑚m lines each contains 2 integers 𝑜𝑝op (1≤𝑜𝑝≤21≤op≤2) and 𝑘k, denoting each operation:
- 𝑜𝑝=1op=1 means a modification on the sequence. Grammy will append an integer 𝑘k (0≤𝑘≤2550≤k≤255) at the end of the sequence so the sequence becomes 𝐴1,𝐴2,…,𝐴𝑁+1A1,A2,…,AN+1 where 𝑁N is the current length of the sequence before modification.
- 𝑜𝑝=2op=2 means a new game starts with the token at position 𝑘k (1≤𝑘≤𝑁1≤k≤N), where 𝑁N is the current length of the sequence. You need to predict the winner of this game.
Output
For each operation with 𝑜𝑝=2op=2, output one line containing "Grammy" if Grammy will win, or "Alice" if Alice will win when they play optimally.
Example
input
Copy
5 5
1 2 3 4 5
1 6
2 5
1 7
2 5
2 1
output
Copy
Alice
Grammy
Alice
挺好的一个博弈题,可惜没想出来==
设\(f[i]\)表示token位于i处先手是否必胜。首先不难想到暴力:对于每次1操作直接添加,2操作的话从后往前暴力dp来处理询问。转移方程\(\forall j > i,\ f[i]\ |=!f[j]\)。初始化f[n] = 0(这个n是当前的n)。
考虑如何进行优化。注意到,对于同一个数A的不同出现位置i、j(设i < j),有如下关系:
- 若f[j] = 0,则f[i] = 1(因为先手在i可以选择直接到j,此时后手变成了先手(必败))。
- 若f[j] = 1,则f[i] = 1(因为这意味着可以从j跳到一个位置k满足f[k] = 0,那么也可以选择直接从i跳到k)
综上,对于同一个数A的不同出现位置\(p_1,p_2...p_n\),\(f[p_1]=f[p_2]=...=f[p_{n-1}] = 1\),只有\(f[p_n]\)不确定。
所以实际上只需要考虑0~255每个数最右侧出现位置的f值即可。代码整体流程就是维护一个数组rmax,rmax[x]表示x这个数当前出现的最右侧的那个位置。此时f数组的定义就需要改变一下,f[i]表示i这个数出现的最右侧位置是否先手必胜,所以f数组的大小仅需开到256。对于每个询问1,直接暴力维护一遍f数组。对于每个询问2,如果输入的k这个位置对应的数的出现的最右侧位置不是k,那么一定先手必胜;如果是的话则根据f值判断这个位置是否先手必胜。关键就在于怎么维护。由之前暴力版本的转移方程可知更新时必须按照位置从大到小进行更新,所以每次维护的时候,需要对0~255这256个数按照最右侧出现位置从大到小排序,这样才能保证dp的正确性。
举个例子,对于数组3 3 4 4 4 5 6 5,如果询问为op = 2, k = 2;此时初始位置为最右侧的3的位置,先手如果要必胜只能考虑最右侧的4,最右侧的5以及最右侧的6这三个位置(否则如果跳到中间的4或者中间的5,轮到了Alice,此时先手必胜,即Alice必胜)。因此暴力更新是没有问题的。
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
int n, m, a[400006], f[257], rmax[257];
bool cmp(int a, int b) {
return rmax[a] > rmax[b];
}
void process() {
memset(f, 1, sizeof(f));
vector<int> v;
for(int i = 0; i < 256; i++) {
if(rmax[i]) {//如果这个数出现过再进行排序 否则没有意义
v.push_back(i);
}
}
sort(v.begin(), v.end(), cmp);
for(auto x : v) {
bool ok = 0;
for(int i = 0; i < 8; i++) {
int tmp = x ^ (1 << i);
if(rmax[tmp] < rmax[x]) continue;
ok |= (!f[tmp]);
}
f[x] = ok;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; i++) {
cin >> a[i];
rmax[a[i]] = i;
}
process();//一定不要忘记在这里也要process
for(int i = 1; i <= m; i++) {
int op, k;
cin >> op >> k;
if(op == 1) {
n++;
a[n] = k;
rmax[k] = n;
process();//暴力维护
} else {
if(rmax[a[k]] > k) {
puts("Grammy");
} else {
if(f[a[k]]) {
puts("Grammy");
} else {
puts("Alice");
}
}
}
}
return 0;
}