Codeforces Round #721 (Div. 2)A、B、C题解

本文探讨了编程竞赛中的题目解决策略,包括如何通过预处理快速找到最大整数k使得特定位数的二进制运算结果为0。同时,文章分析了01回文串博弈的两种难度版本,通过博弈论分析了不同情况下先手和后手的获胜策略,涉及字符串长度的奇偶性和回文状态的影响。最后,介绍了如何统计序列中对数之和的方法,涉及子串、对子出现次数的计算等概念。
摘要由CSDN通过智能技术生成

不会吧不会吧不会吧不会真的有人520不陪npy去熬夜陪毛子打cf还要掉分被绿吧?

在这里插入图片描述
顶不住了,本蒟蒻也要肝题解了,蒟蒻上路如有问题请大佬评论区轻喷指教orz

A. And Then There Were K

传送门:A. And Then There Were K
题意:
给定t(1 ≤ t ≤ 3⋅104)组数据,每组数据一个正整数n (1 ≤ n ≤ 109),求最大的整数k,使得n & (n−1) & (n−2) & (n−3) & … (k) = 0成立。
思路:

  • 水题。令一个二进制数的最高位为第x位(x从0开始计),打表几组数据可以发现将n转为二进制表示之后的x位为1需要与x为0的数字进行&运算才能求得0,x位为0的最大数字是2x-1,而n的第0位-第x-1位在与2x~n-1进行&运算时都会变为0,因此即求2x-1。因此先预处理二进制每一位的权值再遍历寻找符合区间。

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 10000010;
typedef long long ll;
typedef pair<int, int>PII;

int n, m, t, k;
ll w[N];

int main() {
	cin >> t;
	for (int i = 0; i <= 31; i++) {//预处理
		w[i] = (ll)1 << i;
	}
	while (t--) {
		cin >> n;
		for (int i = 0; i <= 31; i++) {
			if (n >= w[i] && n < w[i + 1])//寻找满足条件的区间
			{
				cout << w[i] - 1 << endl;
				break;
			}
		}
	}
}

B1. Palindrome Game (easy version)

传送门:B1. Palindrome Game (easy version)
题意:
给定t(1 ≤ t ≤ 103)组数据,每组数据两行,第一行给定n(1 ≤ n ≤ 103),第二行给定一个长度为n的01回文字符串,保证至少有1个字符‘0’。ALICE先手BOB后手,每个回合可以选择进行两个操作之一:

  1. 选择任意‘0’变为‘1’,消耗1.
  2. 翻转字符串,消耗0,此操作只能在字符串为非回文字符串时进行,且不能连续使用。

当字符串变为全1串时游戏结束,消耗少的一方获胜。

思路:
博弈论。操作一是选择任意‘0’,因此操作二的翻转无意义,相当于跳过自己的回合。给定的为回文串,根据字符串长度奇偶分类讨论。

  1. 字符串长度为偶数:初始的字符串为回文串,因此A起手的操作只能将回文串变为非回文串,B操作为对称A的操作使得字符串变回回文串,循环此操作直至字符串中只剩两个对称的’0‘,此时A、B的消耗相同,字符串为回文串,轮到A操作。A只能执行操作1,B执行翻转,A将最后一个’0‘变为’1‘,A的消耗比B大2,因此字符串长度为偶数时恒为B胜。
    eg:“10000001” 循环操作至“11100111”,A “11110111”,B翻转,A “11111111” 。
  2. 字符串长度为奇数:特判n=1时B胜。再分类字符串中部为’0‘还是’1‘。
    (1)中部为’1‘时,同字符串长度为偶数的操作,恒B胜。
    (2)中部为’0‘时,A起手将中部变为’1‘,此时状态变为(1),角色互换,因此A胜。特判只有一个’0‘且正好在中部的情况,如 “101”,A先手只能操作为“111”游戏结束,B胜

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 10000010;
typedef long long ll;
typedef pair<int, int>PII;

int n, m, t, k;
string str;

int main() {
	cin >> t;
	while (t--) {
		cin >> n >> str;
		if (n == 1) {
			puts("BOB");
			continue;
		}
		if (n % 2 == 1) {
			int sum = 0;
			for (auto t : str) {
				if (t == '0')
					sum++;
			}
			if (str[n / 2] == '1')
				puts("BOB");
			else {
				if (sum > 1)
					puts("ALICE");
				else
					puts("BOB");
			}
		} else {
			puts("BOB");
		}
	}
}

B2. Palindrome Game (hard version)

传送门:Palindrome Game (hard version)

题意:
同B1,差别为初始给定的字符串不一定为回文串。
思路:
先判定初始给定字符串是否为回文串,是的话同B1。否则继续按照字符串长度奇偶性进行分类讨论。

  1. 长度为偶数。在变为回文串之前A一直执行翻转操作,B逐一将字符串向回文串更改。在某次B操作后只剩一个’0‘字符串变为回文串时,A执行1操作将该’0‘变为回文串,此后操作同B1,角色互换,因此恒A胜。
    eg:初始“ 10000111”循环操作至“11000111”,此时A操作为“11100111”,此后操作同B1 。

  2. 长度为奇数。特判中部为’0‘并且整个字符串只有两个’0‘时,平局。
    eg.初始“10011”,A不管执行操作1还是2都是平局。
    此外都是A胜,具体操作雷同。

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 1000010;
typedef long long ll;
typedef pair<int, int>PII;

int n, m, t, k;
int w[N];
string str;

int main() {
	cin >> t;
	while (t--) {
		scanf("%d", &n);
		cin >> str;
		int f = 1;
		int sum = 0;
		//判断是否为回文串并计算字符串中'0'的个数
		for (int i = 0, j = str.size() - 1; i <= n / 2; i++, j--) {
			if (str[i] != str[j]) {
				f = 0;
			}
			if (i != j) {
				if (str[i] == '0')
					sum++;
				if (str[j] == '0')
					sum++;
			} else {
				if (str[i] == '0')
					sum++;
			}
		}
		//不是回文串
		if (!f) {
			if (n & 1) {
				if ( str[n / 2] == '0' && sum == 2) {
					puts("DRAW");
				} else
					puts("ALICE");
			} else {
				puts("ALICE");
			}
		}
		//是回文串 
		else {
			if (n == 1) {
				puts("BOB");
				continue;
			}
			if (n % 2 == 1) {
				int sum = 0;
				for (auto t : str) {
					if (t == '0')
						sum++;
				}
				if (str[n / 2] == '1')
					puts("BOB");
				else {
					if (sum > 1)
						puts("ALICE");
					else
						puts("BOB");
				}
			} else {
				puts("BOB");
			}
		}


	}
}

C. Sequence Pair Weight

传送门:C. Sequence Pair Weight

题意:
给定t(1 ≤ t ≤ 105 )组数据,每组数据两行,第一行n(1 ≤ n ≤ 105),第二行长度为n的序列,其中数值相同的两个数字能凑成一对,求这个序列的所有子串中所含对数之和。

  • 子串是指原序列去除任意长度的前缀和后缀,长度可以为0 。

思路:
太菜了看不出tag,应该就是思维题吧。 统计每一对在所有子串中出现的个数并累加。设一对对子中第一个数字下标为l,第二个数字下标为r,那么显然这对对子在所有子串中出现的个数为l *(n-r+1) 。因此输入时将之前同样数字的下标 l 取出乘上(n-r+1),再将当前下标记录到该数字的数组,但是这样如果同样数字很多的话每次取出 l 复杂度会很高会t。观察到输入到当前时(n-r+1)不变,因此可以合并同类项将之前的下标全部累加至一个数字 l,此时计算当前子串对的个数的复杂度为O(1)。序列中数字范围1~1e9很大但是序列长度小于1e5,因此需要map离散化操作。每轮开始前记得初始化map和 l 数组。

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 1000010;
typedef long long ll;
typedef pair<int, int>PII;

int n, m, t, k;
int w[N];//存储序列
map<int, int>mp;//离散化操作
ll ve[N];//记录离散化后为i的原数字之前出现过的下标之和
int res;
int main() {
	cin >> t;
	while (t--) {
		scanf("%d", &n);
		//初始化
		ll sum = 0;
		mp.clear();
		for (int i = 0; i <= res; i++)
			ve[i] = 0;
		res = 0;//离散化映射标记
		for (int i = 1; i <= n; i++) {
			scanf("%d", &w[i]);
			if (!mp[w[i]])//未离散化过则赋值映射标记
				mp[w[i]] = ++res;
			sum += ve[mp[w[i]]] * (n - i + 1);//累加子串含当前数字的对子的总个数
			ve[mp[w[i]]] += i;//更新当前数字的下标和
		}
		printf("%lld\n", sum);

	}
}
  • 12
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值