比赛链接

2025牛客寒假算法基础集训营5

E.小L的井字棋

题目描述

小L和炸鸡在下井字棋。

对于给定的 列的棋盘,一共有 个位置可以落子。双方可能已经进行了若干次行动,即你拿到的是一个残局局面。我们使用 表示棋盘中从上往下数第 行和从左往右数第 列的单元格,使用 表示这个单元格中现在的状态,状态有且仅有三种:
,表示第 行第 列的格子内小L下了一个子;
,表示第 行第 列的格子内炸鸡下了一个子;
,表示第 行第 列的格子内还没有落子。
谁先让自己的棋子三个连成一线,谁就获胜。三个连成一线指某一行、或者某一列、或者某一对角线棋子都一样。

保证此时棋盘上双方落子数量相同,且还未分出胜负。现在,轮到小L行动了,由于小L是新手,炸鸡自大地给予了小L一个特权:在接下去的某一次行动中可以连续走两步。
在双方都足够聪明的情况下,小L能否必胜?

输入描述

每个测试文件均包含多组测试数据。第一行输入一个整数 代表数据组数,每组测试数据描述如下:
一共三行,第 行输入三个字符 ,表示当前棋盘中第 行的状态。

输出描述

对于每一组测试数据,新起一行。如果小L必胜,输出 ,否则输出

解题思路

没有技巧,全是暴力。

当双方棋子数均时,小L必胜。

当双方棋子数均为4时,只剩一个空,下棋并判断即可。

当双方棋子数均为3时,剩3个位置,由于特权,小L可选择其中任意两个,故存在3种可选下法,枚举判断即可。

完整代码

#include<bits/stdc++.h>
//#define int long long
using namespace std;

int check(int mp[3][3]) {
	for (int i = 0; i < 3; i++) {
		if (mp[i][0] == 1)
			if (mp[i][1] == 1)
				if (mp[i][2] == 1)
					return 1;
		if (mp[0][i] == 1)
			if (mp[1][i] == 1)
				if (mp[2][i] == 1)
					return 1;
	}
	if (mp[0][0] == 1)
		if (mp[1][1] == 1)
			if (mp[2][2] == 1)
				return 1;
	if (mp[0][2] == 1)
		if (mp[1][1] == 1)
			if (mp[2][0] == 1)
				return 1;
	return 0;
}

static string solve() {
	char c;
	int mp[3][3];
	int cntx = 0;
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 3; j++) {
			cin >> c;
			if (c == 'X') {
				mp[i][j] = 1;
				cntx++;
			}
			else if (c == 'O') mp[i][j] = -1;
			else mp[i][j] = 0;
		}
	}
	if (cntx <= 2) return "Yes";
	else if (cntx == 4) {
		for (int i = 0; i < 3; i++)
			for (int j = 0; j < 3; j++)
				if (mp[i][j] == 0)mp[i][j] = 1;
		if (check(mp) == 1)return "Yes";
		else return "No";
	}
	else {
		int space[3][2];
		int cnt = 0;
		for (int i = 0; i < 3; i++)
			for (int j = 0; j < 3; j++)
			{
				if (mp[i][j] == 0) {
					space[cnt][0] = i;
					space[cnt][1] = j;
					cnt++;
				}
			}
		mp[space[0][0]][space[0][1]] = -1;
		mp[space[1][0]][space[1][1]] = 1;
		mp[space[2][0]][space[2][1]] = 1;
		if (check(mp) == 1) return "Yes";
		mp[space[0][0]][space[0][1]] = 1;
		mp[space[1][0]][space[1][1]] = -1;
		mp[space[2][0]][space[2][1]] = 1;
		if (check(mp) == 1) return "Yes";
		mp[space[0][0]][space[0][1]] = 1;
		mp[space[1][0]][space[1][1]] = 1;
		mp[space[2][0]][space[2][1]] = -1;
		if (check(mp) == 1) return "Yes";
		return "No";
	}
}

signed main() {
	int T = 1;
	cin >> T;
	while (T--) {
		cout << solve() << endl;
	}
	return 0;
}

C.小L的位运算

题目描述

炸鸡今天教了小L有关位运算的知识。

小L有三个二进制数 ,三个数字的位数均为 位。每一轮,他可以从以下四种操作中任选一个进行:
花费 元,反置二进制数 的任意一位;
花费 元,反置二进制数 的任意一位;
花费 元,交换二进制数 的任意两个数位;
花费 元,交换二进制数 的任意两个数位。
请你帮小红判断,若能进行任意多轮操作(也可以不进行操作),使得 的最小花费是多少。我们可以证明,一定存在一种操作方案,使得该式子成立。

在二进制表示中,数字 反置后得到 ;数字 反置后得到
其中, 表示按位异或运算。如果您需要更多位运算相关的知识,可以参考 OI-Wiki的相关章节

输入描述

第一行输入三个整数 代表给定的二进制数的位数、操作一二的代价、操作三四的代价。
第二行输入一个长度为 的二进制数
第三行输入一个长度为 的二进制数
第四行输入一个长度为 的二进制数

输出描述

输出一个整数,表示最小需要花费的代价。

解题思路

不难发现,错误的位有四种类型:

  1. 时,
  2. 时,
  3. 时,
  4. 时,

我们依次按 的顺序表示为

对于其中任意一种错误类型,都可以与其他三种错误类型通过交换操作来同时合法化,如 交换 中对应的位,变成 ,从而合法。

我们统计这四种错误类型的数量,分别存入数组 ,而 为所有错误数量之和。

由于交换操作本质是同时对两个位进行反置操作,如果反置两个位的代价小于等于交换操作的代价(即 ),则所有的交换操作均可被反置操作取代。此时答案即为

否则,假设变量 为这四个数中最大的那个,如果 大于 的一半,则 可与除自己外所有的类型通过交换操作来合法化,多余的部分仅能反置;否则,可以证明,一定存在一种匹配方式,使得四种类型可以全部使用交换操作合法化( 为偶数的情况),或仅剩余 1 个位无法交换( 为奇数的情况)。

完整代码

#include<bits/stdc++.h>
#define int long long
using namespace std;

int n, x, y, ans, sum, mx;
string a, b, c;
int w[4];

signed main() {
	cin >> n >> x >> y >> a >> b >> c;
	for (int i = 0; i < n; i++) {
		if (c[i] == '0') {
			if (a[i] == '0' && b[i] == '1') w[0]++;
			else if (a[i] == '1' && b[i] == '0') w[1]++;
		}
		else {
			if (a[i] == '0' && b[i] == '0') w[2]++;
			else if (a[i] == '1' && b[i] == '1') w[3]++;
		}
	}

	sum = w[0] + w[1] + w[2] + w[3];
	mx = max({ w[0],w[1],w[2],w[3] });
	if (x * 2 <= y)
		ans = sum * x;
	else
		if (mx > sum / 2) ans = (sum - mx) * y + (mx * 2 - sum) * x;
		else ans = sum / 2 * y + sum % 2 * x;

	cout << ans << endl;
}

如有错误欢迎指正~