GAME-24 G H I

G-回合制 Nim

https://atcoder.jp/contests/game-24/tasks/game_g

结合和 的题目,要求是每一轮 每个人都必须在 每一堆操作一次

所以如果考虑一堆石子,我们能知道石子数量 S 所对应的 必胜必败态 。

那么我们只需要考虑需要操作的次数最小的那堆石子 的 必胜必败态即可

操作次数就是 $\lfloor \frac{A}{B + 1}\rfloor$ ,先手必胜也就是有余数,所以我们只需要看 操作次数最小的 石子有没有余数即可。

namespace XK {
  void solve() {
  	ll N; rd(N);
  	ll minx = LINF, ans = LINF;
  	rep(i, N) {
  		ll a, b; rd(a, b);
  		if(chmin(minx, a / (b + 1))) 
  			ans = LINF;
  		if(a / (b + 1) == minx) chmin(ans, a % (b + 1));
  	}
  	wt(ans ? "Alice" : "Bob");
  }
};

H-字典序游戏

https://atcoder.jp/contests/game-24/tasks/game_h

其实类似 全状态转移,没有更好的写法,但是这里显式构造字符串显然复杂度不够,于是考虑使用 整数对 {rk[v], c} 来构造字符串,c 就是这一轮选择的, rk 就是之前所有的,这样就可以预处理出本轮的所有 新字符串,然后再将 c 融合入 rk 即可

比较简单的方法是 直接比这个二元组,你当然也可以 定义 key = rk[u] * 26 + (c - 'a')

因为 c 是小写字母,这样可以直接比较 key 的大小

使用一个 dp 数组记录每一条路的选法, 最后回溯即可

namespace XK {
  void solve() {
  	ll N, M, K; rd(N, M, K);
  	V<V<int>> e(N);
  	V<pair<int, char>> ed(M);
  	rep(i, M) {
  		int u, v; char c; rd(u, v, c); -- u, -- v;
  		e[u].push_back(i);
  		ed[i] = {v, c};
  	}

  	V<V<int>> f(2 * K + 1, V<int>(N)); // hui su
  	V<int> rk(N), nrk(N), ord(N);
  	iota(all(ord), 0ll);
  	V<pair<int, char>> now(N);

  	Rep(t, 1, 2 * K + 1) {
  		bool alice = !(t & 1);
  		rep(u, N) {
  			int id = e[u][0];
  			now[u] = {rk[ed[id].fst], ed[id].snd};
  			f[t][u] = id;
  			for(auto eid : e[u]) {
  				auto [v, c] = ed[eid];
  				pair<int, char> can = {rk[v], c};
  				if((alice && can < now[u]) || (!alice && can > now[u])) {
  					now[u] = can;
  					f[t][u] = eid;
  				}
  			}
  		}

  		sort(all(ord), [now](int u, int v){
  			return now[u] < now[v];
  		});

  		nrk[ord[0]] = 0;
  		Rep(i, 1, N) {
  			int u = ord[i - 1], v = ord[i];
  			nrk[v] = nrk[u] + (now[u] != now[v]);
  		}
  		rk.swap(nrk);
  	}

  	string ans(2 * K, ' ');
  	int u = 0;
  	rrep(i, 1, 2 * K + 1) {
  		ans[i - 1] = ed[f[i][u]].snd;
  		u = ed[f[i][u]].fst;
  	}
  	wt(ans);
  }
};

I-项链 2

https://atcoder.jp/contests/game-24/tasks/game_i

玩一下可以发现,想要游戏不断能玩下去的话,最终的操作一定会让其变成一个全是同一个字母的样子。

于是如果令 A,B,C 分别是 1,2,3 的话,任何一种组合操作都不会改变 序列本身的 异或和。

但是最终这个异或和如果是 0 的话,说明操作一定是 变成一个偶数长的 序列,反之则是 奇数长。

而只需要比较一开始和结尾的长度差就能判断操作数的奇偶,以此来判断谁能赢即可。

namespace XK {
  void solve() {
  	ll N; string S; rd(N, S);
  	ll ans = 0;
  	rep(i, N) 
  		ans ^= (S[i] - 'A') + 1;
  	if(((N % 2) + !(ans == 0)) % 2) wt("Alice");
  	else wt("Bob");
  }
};