海亮01/04博弈论杂题

海亮01/04博弈论杂题

题单链接

KH好闪,拜谢KH

T1

AT_agc017_d

题意

有一棵 \(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

AT_abc278_g

题意

  • 给定 \(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

CF1458E

题意

两堆石子,先手后手轮流在一堆中取石子,不能不取。题目给出\(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

CF1511G

题意

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)\)

但是这玩应显然是没法维护的,怎么办呢?

然后就是这道题最神奇的地方了:我们发现一件事情,如果,定义

\[sum_{j,i}=\bigoplus_{c_k\in[i,i+2^j-1]}(c_k-i)\\ cnt_{j,i}=\sum_{c_k\in[i,i+2^j-1]}1 \]

那么如果计算这段区间对一个左端点 \(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

P7078

题意

草原上有 \(n\) 条蛇,编号分别为 \(1, 2, \ldots , n\)。初始时每条蛇有一个体力值 \(a_i\),我们称编号为 \(x\) 的蛇实力比编号为 \(y\) 的蛇强当且仅当它们当前的体力值满足 \(a_x > a_y\),或者 \(a_x = a_y\)\(x > y\)

接下来这些蛇将进行决斗,决斗将持续若干轮,每一轮实力最强的蛇拥有选择权,可以选择吃或者不吃掉实力最弱的蛇:

  1. 如果选择吃,那么实力最强的蛇的体力值将减去实力最弱的蛇的体力值,实力最弱的蛇被吃掉,退出接下来的决斗。之后开始下一轮决斗。
  2. 如果选择不吃,决斗立刻结束。

每条蛇希望在自己不被吃的前提下在决斗中尽可能多吃别的蛇(显然,蛇不会选择吃自己)。

现在假设每条蛇都足够聪明,请你求出决斗结束后会剩几条蛇。

本题有多组数据,对于第一组数据,每条蛇体力会全部由输入给出,之后的每一组数据,会相对于上一组的数据,修改一部分蛇的体力作为新的输入。

题解

先想只有三条蛇的情况。这里设这三条蛇分别为 \(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

CF1704F

题意

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

P8347

题意

给定 \(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

AT_agc048_d

题意

现在有\(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}\) 的时候,后手必胜。

尝试写递推式:

发现

\[f_{l,r}= \begin{cases} 1&a_r<g_{l+1,r}\\ f_{l,r-1}+a_r-g_{l+1,r}+1&a_r\ge g_{l+1,r} \end{cases} \]

其中上面的情况表示如果先手直接取走 \(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;
}

T13

T14

posted @ 2024-01-04 08:22  Call_me_Eric  阅读(9)  评论(0编辑  收藏  举报
Live2D