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;
}