海亮01/04博弈论杂题
海亮01/04博弈论杂题
KH好闪,拜谢KH
T1
题意
有一棵 \(N\) 个节点的树,节点标号为 \(1,2,⋯,N\),边用 \((x_i,y_i)\)表示。 Alice 和 Bob 在这棵树上玩一个游戏,Alice先手,两人轮流操作:
选择一条树上存在的边,把它断开使树变成两个连通块。然后把不包含 \(1\) 号点的联通块删除
当一个玩家不能操作时输,你需要算出:假如两人都按最优策略操作,谁将获胜。
题解
先考虑如果根只有一个子树怎么办?
显然一定是先手胜对叭?
如果是两个呢?
算一下两个子树的 SG
函数值,然后分成两个子游戏即可,最后异或一下就可以了。
但是三个呢?多个子树(\(k\) 个)呢?
发现我们可以将根克隆出 \(k\) 个出来,每一个都连接一个子树。然后发现这个东西将原来的游戏分成了 \(k\) 个子游戏,每个游戏的 SG
都是直接算出子树的 SG
\(+1\) 得到的(加了一条边连向父亲对吧)。
然后就没了。
代码
#include<bits/stdc++.h> using namespace std; inline int read(){ int x = 0,f = 1;char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();} return x * f; } const int maxn = 2e5 + 10; vector<int> edg[maxn]; int n; int dfs(int u,int f){ int res = 0; for(int v : edg[u])if(v != f)res ^= (dfs(v, u) + 1); return res; } signed main(){ n = read();int u, v; for(int i = 1;i < n;i++){ u = read(); v = read(); edg[u].push_back(v);edg[v].push_back(u); } puts(dfs(1, 0) ? "Alice" : "Bob"); return 0; }
T2
T3
题意
- 给定 \(n\),\(l\),\(r\) 三个数,你需要和交互器博弈。
- 有一个长度为 \(n\) 的区间,你和交互器轮流操作,其中先后手由你自己决定。
- 每次操作,操作的一方选择一个没有被染黑并且长度在 \(l\) 和 \(r\) 之间的区间,把它染黑。
- 无法操作的一方寄了,另一方获胜。
- 每次你操作要输出两个数 \(a\) 和 \(b\),表示你选择了区间 \([a,a+b-1]\)。
- 每次交互器操作会给你两个数 \(a\) 和 \(b\),表示交互器选择了 \([a,a+b-1]\),若 \(a=b=0\) 则表示你获胜,如果 \(a=b=-1\) 则表示你寄了。
- \(n\le 2000,1\le l\le r\le n\)
题解
首先发现,如果给定的操作区间能够让你在中心操作一次(你选择的区间的中心与整体的中心重合),那么直接先手选择中心,然后和对手对称选择即可。
然后发现无法这样做当且仅当 \(l=r\space\) 且 \(\space n\not\equiv l\pmod 2\)
然后尝试计算 SG
函数。
发现这个计算是 \(O(n^2)\) 的。
然后先判断 \(SG(n)=0\),如果是的话就后手,否则先手。
然后接下来的操作就寻找一个位置使得操作后整体的 \(SG=0\) 即可。
维护线段可以用 \(set\),但是直接找是 \(O(n)\) 仍然是可以接受的。
代码
#include<bits/stdc++.h> using namespace std; inline int read(){ int x = 0, f = 1;char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();} while(ch >= '0' && ch <= '9'){x =(x << 1) + (x <<3) + (ch ^ 48);ch = getchar();} return x * f; } const int maxn = 5e3 + 10; int n, l, r; int sg[maxn]; bool book[maxn]; set<pair<int,int> > st; void split(int a,int b){ for(auto it = st.begin();it != st.end();it++){ if(it->first <= a && b <= it->second){ int u = it->first, v = it->second; st.erase(it); if(u <= a - 1)st.insert(make_pair(u, a - 1)); if(b + 1 <= v)st.insert(make_pair(b + 1, v)); return; } } } pair<int,int> getseg(){//find a seg to let the SG gets 0 int nsg = 0; for(auto i : st){nsg ^= sg[i.second - i.first + 1];} for(auto it = st.begin();it != st.end();it++){ auto i = *it; int x = nsg ^ sg[i.second - i.first + 1]; for(int j = i.first;j + l - 1 <= i.second;j++){ if((x ^ sg[j - i.first] ^ sg[i.second - j - l + 1]) == 0){ return make_pair(j,j + l - 1); } } } } signed main(){ cin >> n >> l >> r; if(l != r || (n & 1) == (l & 1)){ cout << "First" << endl; int x = ((n & 1) == (l & 1)) ? l : (l + 1); cout << n / 2 - x / 2 + 1 << ' ' << x << endl; while(1){ int a, b; cin >> a >> b; if(a == 0 && b == 0)return 0; cout << n - a - b + 2 << ' ' << b << endl; } } for(int i = l;i <= n;i++){ for(int j = 0;j <= n;j++)book[j] = 0; for(int j = 0;j + l <= i;j++){book[sg[j] ^ sg[i - j - l]] = 1;} int x = 0;while(book[x])x++; sg[i] = x; } int a, b; st.insert(make_pair(1, n)); if(sg[n]){ cout << "First" << endl; } else{ cout << "Second" << endl; cin >> a >> b;b = a + b - 1; split(a, b); } while(1){ auto x = getseg(); cout << x.first << ' ' << x.second - x.first + 1 << endl; split(x.first,x.second); cin >> a >> b; if(a == 0 && b == 0)return 0; b = a + b - 1; split(a, b); } return 0; }
T4
题意
两堆石子,先手后手轮流在一堆中取石子,不能不取。题目给出\(n\)种两堆石头的状态\((x_i,y_i)\),当一个人取石头之前,两堆石头的状态为\((0,0)\)或者以上\(n\)种状态,这个人输了。
假设两者都足够聪明,\(q\)次询问\(a_i,b_i\)表示初始两堆石子的数量为\(a_i,b_i\),先手必胜还是必败,前者输出WIN
后者输出LOSE
。
题解
必须发现的一个性质是,如果没有新加入的必败点,那么所有在 \(y=x\) 上的点都是必败点。
但是现在加入了新的必败点,怎么办呢?
不难发现很难发现,我们先设一个偏移量 \(u\),对于一个必败点 \((x, y)\),分三种情况讨论:
- \(x+u=y\):对偏移量没有影响。
- \(x+u>y\):如果第 \(x\) 行没有删除,那么删除第 \(x\) 行,并且 \(u\gets u+1\)。
- \(x+u<y\):如果第 \(y\) 列没有删除,那么删除第 \(y\) 列,并且 \(u\gets u-1\)。
然后对于查询 \((x, y)\):只有 \((0,0)\to(x,y)\) 的必败点生效,只要 \(x+u=y\),那么必败,否则必胜。
用树状数组+离散化维护即可。
代码
#include<bits/stdc++.h> using namespace std; inline int read(){ int x = 0, f = 1;char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();} while(ch >= '0' && ch <= '9'){x =(x << 1) + (x <<3) + (ch ^ 48);ch = getchar();} return x * f; } const int maxn = 2e5 + 10; int n, m; struct BIT{ int c[maxn]; inline int lowbit(int x){return x & (-x);} void upd(int x,int add){for(;x <= n;x += lowbit(x))c[x] += add;} int qry(int x){int ans = 0;for(;x;x -= lowbit(x))ans += c[x];return ans;} }tr; struct query{ int x, y, id; query(int x = 0,int y = 0,int id = 0):x(x),y(y),id(id){} friend bool operator < (query a,query b){return a.x != b.x ? a.x < b.x : (a.y != b.y ? a.y < b.y : a.id < b.id);} }q[maxn]; vector<int> Y; bool ans[maxn]; bool book[maxn]; signed main(){ n = read(); m = read(); for(int i = 1;i <= n;i++){q[i].x = read();Y.push_back(q[i].y = read());q[i].id = 0;} for(int i = 1;i <= m;i++){q[i + n].x = read();q[i + n].y = read();q[i + n].id = i;} sort(Y.begin(),Y.end());Y.erase(unique(Y.begin(),Y.end()),Y.end());sort(q + 1,q + 1 + m + n); int u = 0, lstx = -1; for(int i = 1;i <= n + m;i++){ int y = upper_bound(Y.begin(),Y.end(),q[i].y) - Y.begin(); if(!q[i].id){ int dir = u - tr.qry(y), d = q[i].x - q[i].y; if(d < dir){ if(!book[y]){book[y] = 1;tr.upd(y,1);} } else{if(d > dir && lstx != q[i].x){u++;lstx = q[i].x;}} } else{ if(q[i].x == q[i - 1].x && q[i].y == q[i - 1].y && !q[i - 1].id){ans[q[i].id] = 0;continue;} if(q[i].x == lstx){ans[q[i].id] = 1;continue;} if((!book[y] || Y[y - 1] != q[i].y) && q[i].y + u - tr.qry(y) == q[i].x){ans[q[i].id] = 0;continue;} ans[q[i].id] = 1; } } for(int i = 1;i <= m;i++)puts(ans[i] ? "WIN" : "LOSE"); return 0; }
T5
T6
题意
Alice 和 Bob 有一个 \(n\times m\) 的棋盘。每行恰好有一个棋子。
他们以如下的方式进行一个游戏:
- 选择一对整数 \(l,r(1\leq l\leq r\leq m)\),将除第 \(l\sim r\) 列之外的部分从棋盘上移除。
- 轮流进行操作,Alice 先手。每一次操作,玩家需要选择一个棋子,并将其向左走任意正整数步(不能移出棋盘)。第一个不能移动的玩家判负。
给定 \(q\) 次独立的询问如 \(L_i,R_i\) 所示,试求如果第一步中选择的整数是 \((L_i,R_i)\),谁会赢得游戏。
\(1\leq n,m,q\leq 2\times 10^5\)。
题解
不难发现,这道题要求的就是 \(\bigoplus_{c_i\in[L,R]}(c_i-L)\)。
但是这玩应显然是没法维护的,怎么办呢?
然后就是这道题最神奇的地方了:我们发现一件事情,如果,定义
那么如果计算这段区间对一个左端点 \(l\) 的贡献,那么有 \(ans=sum_{j,i}\oplus([cnt_{j,i}\equiv1\pmod2]\times (j))\)。
因为这段区间长度一定不会超过 \(2^j\),故超出来的贡献可以直接算。然后就可以快乐倍增了。
代码
#include<bits/stdc++.h> using namespace std; inline int read(){ int x = 0, f = 1;char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();} return x * f; } const int maxn = 2e5 + 10; int n ,m, q; int cnt[maxn], sum[21][maxn]; signed main(){ n = read(); m = read(); for(int i = 1;i <= n;i++){cnt[read()]++;} for(int i = 1;i <= m;i++)cnt[i] += cnt[i - 1]; for(int j = 1;j <= 20;j++){ for(int i = 1;i + (1 << j) - 1 <= m;i++){ sum[j][i] = (sum[j - 1][i] ^ sum[j - 1][i + (1 << (j - 1))]) ^ (((cnt[i + (1 << j) - 1] - cnt[i + (1 << (j - 1)) - 1]) & 1) << (j - 1)); } } q = read(); for(int i = 1;i <= q;i++){ int l = read(), r = read(); int ans = 0; for(int j = 20;j + 1;j--){ if(l + (1 << j) <= r){ ans ^= sum[j][l];l += (1 << j); ans ^= ((cnt[r] - cnt[l - 1]) & 1) << j; } } putchar(ans ? 'A' : 'B'); } putchar('\n'); return 0; }
T7
T8
题意
草原上有 \(n\) 条蛇,编号分别为 \(1, 2, \ldots , n\)。初始时每条蛇有一个体力值 \(a_i\),我们称编号为 \(x\) 的蛇实力比编号为 \(y\) 的蛇强当且仅当它们当前的体力值满足 \(a_x > a_y\),或者 \(a_x = a_y\) 且 \(x > y\)。
接下来这些蛇将进行决斗,决斗将持续若干轮,每一轮实力最强的蛇拥有选择权,可以选择吃或者不吃掉实力最弱的蛇:
- 如果选择吃,那么实力最强的蛇的体力值将减去实力最弱的蛇的体力值,实力最弱的蛇被吃掉,退出接下来的决斗。之后开始下一轮决斗。
- 如果选择不吃,决斗立刻结束。
每条蛇希望在自己不被吃的前提下在决斗中尽可能多吃别的蛇(显然,蛇不会选择吃自己)。
现在假设每条蛇都足够聪明,请你求出决斗结束后会剩几条蛇。
本题有多组数据,对于第一组数据,每条蛇体力会全部由输入给出,之后的每一组数据,会相对于上一组的数据,修改一部分蛇的体力作为新的输入。
题解
先想只有三条蛇的情况。这里设这三条蛇分别为 \(x,y,z\) 且 \(a_x<a_y<a_z\)。
然后考虑 \(z\) 吃不吃 \(x\)。
显然如果 \(a_z-a_x\ge a_y\) 的情况下一定吃,否则一定不吃。
然后考虑多条蛇的情况。
每一轮我们只考虑最大和最小,次小三条蛇(如果只有两条蛇当然一定吃,这里考虑剩余蛇多余三条的情况)。
不难发现,如果这条最大的蛇在吃掉最小的这条蛇之后,仍然比次小的蛇大,显然一定吃,证明显然。
如果吃了之后变成最小的蛇,怎么办呢,一定不吃吗?
我们先预演一下,让这条最大的蛇(设为 \(u\))吃掉最小的蛇,变成了最小的蛇,那么我们来看接下来的次大蛇 \(v\) 会怎么想。
如果 \(v\) 吃掉 \(u\) 之后不是最小蛇,或者只剩下两条蛇,那么显然 \(v\) 一定吃掉 \(u\),否则 \(v\) 也会进行预演对叭。
我们发现,如果按照顺序,需要预演的蛇的编号是 \(x_1,x_2,\dots,x_k\),不难发现,\(x_k\) 的预演一定成功,也就是说,只要 \(x_{k-1}\) 敢吃最小的那条蛇,那么它就一定会死,所以它为了保命,一定不会吃。
然后 \(x_{k-2}\) 发现无论如何 \(x_{k-1}\) 都不会吃掉它,于是它就可以放心大胆的吃。
于是你就会发现,第一条预演的蛇的抉择决定于 \(k\) 的奇偶性,如果 \(k\) 是偶数就一定不吃,否则一定吃,下一条一定不吃。
不难发现,如果出现最大蛇吃掉最小蛇之后变成最小蛇,那么进行轮数不会超过两次,每次预演时间复杂度 \(O(nS)\),其中 \(S\) 是维护最大最小蛇的时间复杂度。当然,不出现这种情况就不需要预演,最多进行轮数不会超过 \(n\) 次,时间复杂度仍然是 \(O(nS)\) 的。
如果用平衡树(或者堆)维护的话 \(S=\log n\),总复杂度是 \(O(Tn\log n)\) 的,还有常数,过不去。
不难发现,每次吃掉蛇之后如果放在另一个双端队列里,仍然满足单调性,于是用两个双端队列维护,就有 \(S=1\) 的,总复杂度就变成了 \(O(Tn)\)了。
代码
#include<bits/stdc++.h> using namespace std; const int maxn = 1e6 + 100; int T, n, a[maxn],k; int main(){ scanf("%d",&T); scanf("%d",&n); /* if(n == 3){ if(a[3] - a[1] > a[2]){ printf("1\n"); } else puts("3"); T--; while(T--){ int x,y; scanf("%d",&k); for(int i = 1;i <= k;i++){ scanf("%d%d",&x,&y); a[x] = y; } if(a[3] - a[1] > a[2]){ puts("1"); } else puts("3"); } }*/ for(int _i = 1;_i <= T;_i++){ if(_i == 1){ for(int i = 1;i <= n;i++){ scanf("%d",&a[i]); } } else { int x, y; scanf("%d",&k); for(int i = 1;i <= k;i++){ scanf("%d%d",&x,&y); a[x] = y; } } deque<pair<int,int>> q1,q2; for(int i = 1;i <= n;i++){ q1.push_back({a[i],i}); } int ans; while(1){ if(q1.size() + q2.size() == 2){ ans = 1; break; } int x,y,id; y = q1.front().first; q1.pop_front(); if(q2.empty() || !q1.empty() && q1.back() > q2.back()){ x = q1.back().first; id = q1.back().second; q1.pop_back(); } else{ x = q2.back().first; id = q2.back().second; q2.pop_back(); } pair<int,int> now = make_pair(x - y,id); if(q1.empty() || q1.front() > now){ ans = q1.size() + q2.size() + 2; int cnt = 0; while(1){ cnt++; if(q1.size() + q2.size() + 1 == 2){ if(cnt % 2 == 0)ans--; break; } int x,id; if(q2.empty() || !q1.empty() && q1.back() > q2.back()){ x = q1.back().first; id = q1.back().second; q1.pop_back(); } else{ x = q2.back().first; id = q2.back().second; q2.pop_back(); } now = {x - now.first,id}; if(!((q1.empty() || q1.front() > now) && (q2.empty() || q2.front() > now))){ if(cnt % 2 == 0)ans--; break; } } break; }//if(q1.empty() || q1.front() > now){ else{ q2.push_front(now); } }//while(1){ printf("%d\n",ans); }//for(int _i = 1;_i <= T;_i++){ return 0; }
T9
题意
Alice 和 Bob 在玩游戏。有 \(n\) 个格子排成一行,每个格子被涂成了红色或蓝色。 Alice 每次操作选择两个相邻的格子,要求其中至少有一个是红色,然后把这两个格子涂成白色。 Bob 每次操作选择两个相邻的格子,要求其中至少有一个是蓝色,然后把这两个格子涂成白色。 他们轮流进行操作(Alice 先手),不能操作的人就输了。 现在给定初始局面,请问在他们都足够聪明的前提下,谁会获得胜利?
题解
首先先判断颜色数量,如果存在一个大于另一个,那么这个显然一定赢(每次都选择一个自己颜色,另一个尽可能选择对方颜色,实在没有选择白色,然后就赢了)
现在只考虑双方持有的颜色数量相同的情况。
不难发现,双方一定先尽可能选择两色交替的段,因为这样能够尽可能消除对方的颜色。
在这个过程中双方的颜色总是相等,那么谁先进入无法同时消除两个颜色的状态,谁输掉游戏。
设计 SG
函数,发现 \(SG(0),SG(1)\) 是必败态为 \(0\)。
然后 \(SG(i)=mex_{j\in[0,i-2]}(SG(j)\oplus SG(i - 2 - j))\)。
发现这玩应没什么优化前途只能 \(O(n^2)\),但是如果打出来表之后就会发现有循环节。
需要注意的是,在最开始的一段可能循环节中有变化,需要特判。
然后就没了。打表找循环节在代码中有。
代码
#include<bits/stdc++.h> using namespace std; inline int read(){ int x = 0, f = 1;char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();} while(ch >= '0' && ch <= '9'){x =(x << 1) + (x <<3) + (ch ^ 48);ch = getchar();} return x * f; } const int maxn = 5e5 + 10; int sg[maxn]; bool book[maxn]; int n; char ch[maxn]; int table[40] = { 1, 1, 2, 0, 3, 1, 1, 0, 3, 3, 2, 2, 4, 4, 5, 5, 9, 3, 3, 0, 1, 1, 3, 0, 2, 1, 1, 0, 4, 5, 3, 7, 4, 8 }; int SG(int x){ x -= 2;if(x < 0)return 0; switch(x){ case 13:return 0; case 15:return 2; case 16:return 2; case 30:return 2; case 33:return 0; case 50:return 2; default:return table[x % 34]; } } void init(){ for(int i = 2;i <= n;i++){ for(int j = 0;j <= n;j++)book[j] = 0; for(int j = 0;j <= i - 2;j++){book[sg[j] ^ sg[i - 2 - j]] = 1;} int x = 0;while(book[x])x++; sg[i] = x; } for(int i = 0;i <= n;i++){ if(SG(i) != sg[i]){ printf("wrong on line %d,sg = %d, SG = %d\n",i,sg[i],SG(i)); } } } void solve(){ n = read();scanf("%s",ch + 1); int R = 0, B = 0; for(int i = 1;i <= n;i++){ R += (ch[i] == 'R'); B += (ch[i] == 'B'); } if(R != B){puts(R > B ? "Alice" : "Bob");return;} int ans = 0, j = 1; for(int i = 1;i <= n;i = j){ j = i + 1; while(j <= n && ch[j] != ch[j - 1])j++; ans ^= SG(j - i); } puts(ans ? "Alice" : "Bob"); } signed main(){ int T = read(); while(T--){solve();} return 0; } /* 0 0 1 1 2 0 3 1 1 0 3 3 2 2 4 0 5 2 2 3 3 0 1 1 3 0 2 1 1 0 4 5 2 7 4 0 1 1 2 0 3 1 1 0 3 3 2 2 4 4 5 5 2 3 3 0 1 1 3 0 2 1 1 0 4 5 3 7 4 8 1 1 2 0 3 1 1 0 3 3 2 2 4 4 5 5 9 3 3 0 1 1 3 0 2 1 1 0 4 5 3 7 4 8 1 1 2 0 3 1 1 0 3 3 2 2 4 4 5 5 9 3 3 0 1 1 3 0 2 1 1 0 4 5 3 7 4 8 1 1 2 0 3 1 1 0 3 3 2 2 4 4 5 5 9 3 3 0 1 1 3 0 2 1 1 0 4 5 3 7 4 8 1 1 2 0 3 1 1 0 3 3 2 2 4 4 5 5 9 3 3 0 1 1 3 0 2 1 1 0 4 5 3 7 4 8 1 1 2 0 3 1 1 0 3 3 2 2 4 4 5 5 9 3 3 0 1 1 3 0 2 1 1 0 4 5 3 7 4 8 1 1 2 0 3 1 1 0 3 3 2 2 4 4 5 5 9 3 3 0 1 1 3 0 2 1 1 0 4 5 3 7 4 8 1 1 2 0 3 1 1 0 3 3 2 2 4 4 5 5 9 3 3 0 1 1 3 0 2 1 1 */
T10
题意
给定 \(n\) 个节点的树(保证 \(n\ge 2\)),Hifuu 和 Luna 交替操作,前者先手。每回合操作者选择一个节点,将「该节点」和「所有与该节点相连的边」删除,形成若干个连通块,操作者再从中保留一个连通块。如果该回合结束后只剩下一个节点,则该回合的操作者失败,另一个人胜利。问谁存在必胜策略。
题解
先说结论,只要有一个点的度数是偶数那么先手必胜,否则先手必败。
然后尝试证明这个结论。
设有一个点的度数是偶数的状态是 \(A\),剩下的状态是 \(B\)。
我们知道,如果设必胜态是 \(N\),必败态是 \(P\),那么显然有 \(N\to P,P\not\to P\)。
然后回来看这道题,如果所有点的度数都是奇数,显然删除任意一个点都会导致相邻点的度数变成偶数,于是剩下的连通块都一定有至少一个点的度数是偶数,也就是说,\(B\not\to B,B\to A\)。
接下来看 \(A\) 状态能去哪。
我们发现,如果我们找到最深的度数为偶数的点 \(u\),然后删除它的父亲 \(fa_u\),然后就发现,以 \(u\) 为根的子树一定都是度数为奇数的点。(如果还有度数未偶数的点就与深度最深相悖)
证完了。
代码
#include<bits/stdc++.h> using namespace std; inline int read(){ int x = 0, f = 1;char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();} while(ch >= '0' && ch <= '9'){x =(x << 1) + (x <<3) + (ch ^ 48);ch = getchar();} return x * f; } const int maxn = 1e5 + 10; int d[maxn], n; void solve(){ n = read();for(int i = 1;i <= n;i++)d[i] = 0; for(int i = 1;i < n;i++){d[read()]++;d[read()]++;} for(int i = 1;i <= n;i++) if((d[i] & 1) == 0){puts("Hifuu");return;} puts("Luna"); } signed main(){ int T = read(); while(T--){solve();} return 0; }
T11
T12
题意
现在有\(N\)堆石子,第\(i\)石子有\(a_i\)个,现在有两个人(Firstleft和SecondRight)玩这个游戏,Firstleft先手。
每一轮,Firstleft可以从最左边一个至少有一颗石子的堆中拿走至少一颗(最多拿完),然后,SecondRight可以从最右边一个至少有一颗石子的堆中拿走至少一颗(最多拿完)。问先手有无必胜策略。
题解
直接做似乎没什么太好的思路(
然后提供一个神奇的思路:
首先设 \(b_i\) 表示进行到某个局面的时候第 \(i\) 堆的石子数量,\(a_i\) 表示最初的局面石子数量。
设 \(f_{l,r}\) 表示如果满足 \(\forall i \in[l+1,r],b_i=a_i\),当 \(b_l\ge f_{l,r}\) 的时候,先手必胜。
同理设 \(g_{l,r}\) 表示如果满足 \(\forall i \in[l,r-1],b_i=a_i\),当 \(b_r\ge g_{l,r}\) 的时候,后手必胜。
尝试写递推式:
发现
其中上面的情况表示如果先手直接取走 \(l\) 这一堆石子,后手就会陷入必败的状态(满足定义)
下面的情况就是先手如果直接取走 \(l\) 这一堆石子,那么后手就会必胜,所以先手只能一个一个取石子,争取在自己手里这一堆石子被取完之前,先让后手无法保证自己必胜,同时对峙之后自己还得必胜,于是就有不等式 \(b_l-f_{l,r-1}>b_r-g_{l+1,r}\)。然后按照定义式,\(f_{l,r}=\min b_l\),整理可以得到上式。
当然 \(g_{l,r}\) 同理。
然后就没了,直接递推即可。
代码
#include<bits/stdc++.h> #define int long long using namespace std; inline int read(){ int x = 0, f = 1;char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();} while(ch >= '0' && ch <= '9'){x =(x << 1) + (x <<3) + (ch ^ 48);ch = getchar();} return x * f; } const int maxn = 2e2 + 10; int f[maxn][maxn], g[maxn][maxn]; int n, a[maxn]; void solve(){ n = read(); for(int i = 1;i <= n;i++){ a[i] = read();f[i][i] = g[i][i] = 1; } for(int l = n;l;l--){ for(int r = l + 1;r <= n;r++){ f[l][r] = (a[r] < g[l + 1][r]) ? 1 : (f[l][r - 1] + a[r] - g[l + 1][r] + 1); g[l][r] = (a[l] < f[l][r - 1]) ? 1 : (g[l + 1][r] + a[l] - f[l][r - 1] + 1); } } puts(a[1] >= f[1][n] ? "First" : "Second"); } signed main(){ int T = read(); while(T--){solve();} return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】