Educational Codeforces Round 110 (Div. 2) 个人题解 A~D

C题过得确实有点惊险哈,快1:57才过,只剩三四分钟了……

ABCD

A. Fair Playoff

题意

给a,b,c,d四个人的能力值,然后a,b较量,c,d较量,赢的人再进入决赛。问决赛的两人是不是能力值最大的,是就公平,不是就不公平
分析

嘛,怎么写都可以吧
给一份相对复杂的代码

#include <bits/stdc++.h>
#define fors(i, a, b) for(int i = (a); i <= (b); ++i)
#define lson k<<1
#define rson k<<1|1
#define pb push_back
#define lowbit(x) ((x)&(-(x)))
#define mem(a) memset(a, 0, sizeof(a))
#define IOS ios::sync_with_stdio(false), cin.tie(0)
#define int long long
const int inf = 0x3f3f3f3f;
const double dinf = 1e100;
typedef long long ll;
//const ll linf = 9223372036854775807LL;
// const ll linf = 1e18;
using namespace std;
 
signed main()
{
	IOS;
	int  t;
	cin >> t;
	while(t--)
	{
		int a, b, c, d;
		cin >> a >> b >> c >> d;
		int p[4] = {a, b, c, d};
		sort(p, p + 4, greater<int>());
		if(a < b) swap(a, b);
		if(c < d) swap(c, d);
		if(a < c) swap(a, c);
		if(a == p[0] && c == p[1]) cout << "YES" << endl;
		else cout << "NO" << endl;
	}
	return 0;
}

B. Array Reodering

题意

给定一个数组,现在要你重排这个数组,使得有最多的数对,满足:
1 ≤ i < j ≤ n , g c d ( a i , 2 a j ) > 1 1≤i<j≤n, gcd(a_i,2a_j)>1 1ijn,gcd(ai,2aj)>1
分析

贪心。由于数据只有2000,凹了一个 O ( n 2 ) O(n^2) O(n2)的贪心QWQ。先不看条件 1 ≤ i < j ≤ n 1≤i<j≤n 1ijn,算出每个数和其他所有数最多形成的数对。然后,如果我们加上了条件 1 ≤ i < j ≤ n 1≤i<j≤n 1ijn,那么每个数肯定会损失一些数对。贪心的构造是让整个数组损失的数对最小。那么可以猜想,结果是按每个数原本的数对个数降序排序。

粗略证明

(昨天随便想的,但感觉不太严格)

由于把“和其他所有数最多形成的数对”最大的数排在了第一个,故这个最大的数肯定不会有任何损失。进一步地思考,把形成数对大的尽量放前面,其后面的数就越多,前面的数就越少,其原本的数对满足 1 ≤ i < j ≤ n 1≤i<j≤n 1ijn的机会就越多,所以损失的数对就会越少。因为形成数对大的数损失的机会是更大的,所以需要把它们放在前面,减少损失机会。

代码

#include <bits/stdc++.h>
#define fors(i, a, b) for(int i = (a); i <= (b); ++i)
#define lson k<<1
#define rson k<<1|1
#define pb push_back
#define lowbit(x) ((x)&(-(x)))
#define mem(a) memset(a, 0, sizeof(a))
#define IOS ios::sync_with_stdio(false), cin.tie(0)
#define int long long
const int inf = 0x3f3f3f3f;
const double dinf = 1e100;
typedef long long ll;
//const ll linf = 9223372036854775807LL;
// const ll linf = 1e18;
using namespace std;
const int maxn = 2005;
int n;
struct node{
	int x, cnt;
	node(){
		x = cnt = 0;
	}
}a[maxn];
bool cmp(node x, node y){
	return x.cnt > y.cnt;
}
signed main()
{
	IOS;
	int  t;
	cin >> t;
	while(t--)
	{
		int ans = 0;
		cin >> n;
		fors(i, 1, n) cin >> a[i].x, a[i].cnt = 0;
		fors(i, 1, n){
			fors(j, 1, n){
				if(i == j) continue;
				if(__gcd(a[i].x, a[j].x * 2) > 1){
					a[i].cnt++;
				}
			}
		}
		sort(a + 1, a + 1 + n, cmp);
		fors(i, 1, n){
			a[i].cnt = 0;
		}
		fors(i, 1, n){
			fors(j, i + 1, n){
				if(__gcd(a[i].x, a[j].x * 2) > 1){
					ans++;
				}
			}
		}
		cout << ans << endl;
	}
	return 0;
}

C. Unstable String

题意

(题意稍微有点难理解)

给一个字符串,其含有’0’,‘1’,’?’. 其中,’?‘可以看成’1’,也可以看成’0’。如果一个字符串可以是0和1交替出现的,就称这个字符串是 b e a u t i f u l beautiful beautiful。问现在这个字符串有多少 b e a u t i f u l beautiful beautiful的子串?

例如,
字符串1,10是beautiful的;
字符串1???1也是beautiful的,因为通过问号可以转化为10101;
但字符串1???1不是beautiful的,因为通过问号不管怎么变,都不可能使得01交替出现。

分析

后面还有队友的dp做法,思维量和代码复杂度都暴打我的双指针法orz

我的做法是双指针。(开局就想到了,但奈何我的讨论不细致,导致一个多小时以后才终于AC)

设两个指针 p 1 p1 p1, p 2 p2 p2,都从 0 0 0开始,然后 p 2 p2 p2前移,期间检查 p 1 p1 p1 p 2 p2 p2是否可以组成一个 b e a u t i f u l beautiful beautiful的子串,如果可以就继续前移。

如何判断呢?我的做法是:

首先预处理,对每个连续的问号段,维护这整个问号段前面的那个数字。

为什么这样维护呢? 可以发现,当一个问号段长度是奇数时,如果其左右的数字都相同,那么一定可以把问号替换成数字,使得问号串加上两个数字仍然是 b e a u t i f u l beautiful beautiful的。例如,1???1可以替换成10101。反之,如果两端的数字不同,那么这个问号段就不可能保证加上两端的数字后还能 b e a u t i f u l beautiful beautiful。 对于问号段长度为偶数的情况则恰好相反。特别注意,如果问号段位于整个字符串的最左/最右端,那么它是肯定符合条件的

所以,判断问号后面的数字可不可以接,关键是看这个数字和问号段最前面的数字的关系,预处理出来即可。之后再讨论可否前移就比较清晰了。

例如,对于10011???,对于问号段"???",其 p r e [ i ] = 4 pre[i]=4 pre[i]=4,表示最前面的数字下标为4.


那如果通过判断,发现 p 2 p2 p2左移后不能保证 [ p 1 , p 2 ] [p1,p2] [p1,p2]仍然 b e a u t i f u l beautiful beautiful,该怎么办呢?

  1. 如果 p 2 p2 p2 p 2 + 1 p2+1 p2+1都不是问号,说明这是两个相等的数字,无论如何它们都不能连在一起了,直接算出 p 1 p1 p1 p 2 p2 p2的答案加到 a n s ans ans中。 p 1 p1 p1 p 2 p2 p2的所有子串都是符合条件的,所以答案是 ∑ i = 1 p 2 − p 1 + 1 i \sum_{i=1}^{p2-p1+1}i i=1p2p1+1i,一个等差数列和。最后把 p 2 p2 p2右移,然后令 p 1 = p 2 p1=p2 p1=p2(原来的区间已经统计完毕,可以彻底抛弃掉了)
  2. 如果 p 2 p2 p2是问号, p 2 + 1 p2+1 p2+1不是问号,说明 p 2 p2 p2所在的问号段的前面那些已经不可能再接上了。但是,从 p r e [ p 2 ] + 1 pre[p2]+1 pre[p2]+1 p 2 p2 p2这个问号段不一定不再起作用,但可以肯定的是从 p 1 p1 p1 p r e [ p 2 ] pre[p2] pre[p2]肯定不需要了,因此统计出 p 1 p1 p1 p r e [ p 2 ] pre[p2] pre[p2]的等差和。 这个时候不要急着令 p 1 = p r e [ p 2 ] + 1 p1=pre[p2]+1 p1=pre[p2]+1,而要考虑,从 p 1 p1 p1 p r e [ p 2 ] pre[p2] pre[p2],其和 p r e [ p 2 ] pre[p2] pre[p2] p 2 p2 p2仍然可以相连,组成 b e a u t i f u l beautiful beautiful串,记得把这些也统计进来。之后再令 p 1 = p r e [ p 2 + 1 ] p1=pre[p2+1] p1=pre[p2+1]

代码

#include <bits/stdc++.h>
#define fors(i, a, b) for(int i = (a); i <= (b); ++i)
#define lson k<<1
#define rson k<<1|1
#define pb push_back
#define lowbit(x) ((x)&(-(x)))
#define mem(a) memset(a, 0, sizeof(a))
#define IOS ios::sync_with_stdio(false), cin.tie(0)
#define int long long
const int inf = 0x3f3f3f3f;
typedef long long ll;
//const ll linf = 9223372036854775807LL;
// const ll linf = 1e18;
using namespace std;
const int maxn = 2e5 + 10;
int n;
string s;
int cnt(int len)
{
	return (len + 1) * len / 2LL;
}
int cnt(int l, int r)
{
	return (r - l + 1) * (r - l + 2) / 2LL;
}
int pre[maxn];
signed main()
{
	IOS;
	// 前缀处理:对每个?,找其公共序列的最前面一个非问号
	int  t;
	cin >> t;
	
	while(t--)
	{
		// 0和1肯定不能连续. 对于?,按整个问号序列长度定
		// 双指针
		cin >> s;
		int p1 = 0, p2 = 0;
		for(int i = 0; i < s.size(); ++i){
			pre[i] = -1;
		}
		for(int i = 0; i < s.size(); ++i){
			if(i == 0 && s[i] == '?'){
				pre[i] = -1;
			}
			else if(i > 0 && s[i] == '?' && s[i - 1] == '?'){
				pre[i] = pre[i - 1];
			}
			else if(s[i] == '?'){
				pre[i] = i - 1;
			}
		}
		int ans = 0;
		for(;;){
			if(p2 + 1 >= s.size()){
				if(p2 == s.size() - 1){
					ans += cnt(p2 - p1 + 1);
				}
				break;
			}
			int flag = 1;
			if(s[p2] == '?' && s[p2 + 1] == '?');
			else if(s[p2] == '?' && pre[p2] == -1);
			else if(s[p2] == '?' && s[p2 + 1] != '?'){
				int len = p2 - pre[p2];
				if(len % 2 == 0 && s[p2 + 1] != s[pre[p2]]);
				else if(len % 2 != 0 && s[p2 + 1] == s[pre[p2]]);
				else flag = -1;
			}
			else if(s[p2] != '?' && s[p2] == s[p2 + 1]) flag = 0;
			if(flag == 1) p2++;
			else if(flag == 0){
				ans += cnt(p2 - p1 + 1);
				p2++;
				p1 = p2;
			}
			else{
				ans += cnt(pre[p2] - p1 + 1) + (pre[p2] - p1 + 1) * (p2 - pre[p2]);
				p1 = pre[p2] + 1;
				p2++;
			}
		}
		cout << ans << endl;
	}
	return 0;
}

C的DP做法

动态规划太强啦!

d p [ i ] [ 0 ] , d p [ i ] [ 1 ] dp[i][0],dp[i][1] dp[i][0]dp[i][1]分别表示以第 i i i个数为结尾,第 i i i个数为0,为1的答案。则 d p [ i ] [ 0 ] = d p [ i ] [ 1 ] + 1 , d p [ i ] [ 1 ] = d p [ i ] [ 0 ] + 1 dp[i][0]=dp[i][1]+1,dp[i][1]=dp[i][0]+1 dp[i][0]=dp[i][1]+1,dp[i][1]=dp[i][0]+1。遇到问号的时候,将 d p [ i ] [ 1 ] dp[i][1] dp[i][1] d p [ i ] [ 0 ] dp[i][0] dp[i][0]都更新了,遇到数字只更新对应的即可。由于以任何数结尾都可以成一个子串,故每次更新 d p dp dp都要更新答案。

代码

#include <bits/stdc++.h>
#define fors(i, a, b) for(int i = (a); i <= (b); ++i)
#define lson k<<1
#define rson k<<1|1
#define pb push_back
#define lowbit(x) ((x)&(-(x)))
#define mem(a) memset(a, 0, sizeof(a))
#define IOS ios::sync_with_stdio(false), cin.tie(0)
#define int long long
const int inf = 0x3f3f3f3f;
const double dinf = 1e100;
typedef long long ll;
//const ll linf = 9223372036854775807LL;
// const ll linf = 1e18;
using namespace std;
const int maxn = 2e5 + 10;
int n;
int dp[maxn][2] = {0};
string s;
signed main()
{
	IOS;
	int t;
	cin >> t;
	while(t--)
	{
		cin >> s;
		int ans = 1;
		for(int i = 0; i < s.size(); ++i) dp[i][1] = dp[i][0] = 0;
		if(s[0] == '1') dp[0][1] = 1;
		else if(s[0] == '0') dp[0][0] = 1;
		else dp[0][0] = dp[0][1] = 1;
		
		for(int i = 1; i < s.size(); ++i){
			if(s[i] == '0') dp[i][0] = dp[i - 1][1] + 1;
			else if(s[i] == '1') dp[i][1] = dp[i - 1][0] + 1;
			else{
				dp[i][1] = dp[i - 1][0] + 1;
				dp[i][0] = dp[i - 1][1] + 1;
			}
			ans += max(dp[i][1], dp[i][0]);
		}
		cout << ans << endl;
	}
	return 0;
}

D. Playoff Tournament

题意

有一个淘汰赛,比赛情况用一棵节点数为 2 k − 1 2^k-1 2k1的完全二叉树表示(参赛者有 2 k 2^k 2k个)。其中叶子结点从左到右依次表示1和2比赛,3和4比赛,5和6比赛,……二叉树节点值为0表示数字小的胜出,值为1表示数字大的胜出,值为’?'表示不确定。问最后的冠军有几个可能。

给定一个字符串 s s s,从左到右为按时间顺序的各个比赛情况。比如 k = 3 k=3 k=3 s = 0110 ? 11 s=0110?11 s=0110?11,前四个表示1和2比,1赢;3和4比,4赢;5和6比,6赢;7和8比,7赢;后面的“?1"是四强比赛的结果,最后的"1"是决赛的结果。

另外,给 q q q次操作,每次操作都会修改第 x x x场比赛的结果,每次修改后询问现在的冠军可能数。

分析

很简单的模拟。按题意要求构造一个二叉树,复杂度 O ( 2 k O(2^k O(2k)。如果是叶子结点,节点是问号就有2种可能,不是就是一种可能。如果不是叶子结点,节点是1,可能数等于右子节点;节点是0,可能数等于左子节点;节点是问号,可能数等于左右子节点的和。修改后,从被修改的节点开始往上更新一下即可,更新的复杂度为 O ( k ) O(k) O(k),总复杂度为 O ( 2 k + q × k ) O(2^k+q×k) O(2k+q×k)

代码

由于自底向上建堆不熟,所以我把字符串倒置了,所以1对应的变成了左子节点,0对应变成了右子节点。

/**
  * @file    :vsDebug2.cpp
  * @brief   :Educational 110 D
  * @date    :2021-06-05
  * @Motto   :Love Sakurai Yamauchi Forever
  */
#include <bits/stdc++.h>
#define fors(i, a, b) for(int i = (a); i <= (b); ++i)
#define lson k<<1
#define rson k<<1|1
#define pb push_back
#define lowbit(x) ((x)&(-(x)))
#define mem(a) memset(a, 0, sizeof(a))
#define IOS ios::sync_with_stdio(false), cin.tie(0)
#define int long long
const int inf = 0x3f3f3f3f;
const double dinf = 1e100;
typedef long long ll;
//const ll linf = 9223372036854775807LL;
// const ll linf = 1e18;
using namespace std;
const int maxn = (1 << 20) + 500;
int k;
string s;
int a[maxn] = {0};
inline void update(int k)
{
	if((k << 1) > s.size()){
		if(s[k - 1] == '?') a[k] = 2;
		else a[k] = 1;
		return;
	}
	if(s[k - 1] == '?') a[k] = a[lson] + a[rson];
	else if(s[k - 1] == '1') a[k] = a[lson];
	else a[k] = a[rson];
}
void build(int k)
{
	if(k > s.size()) return;
	if((k << 1) > s.size())
	{
		if(s[k - 1] == '?') a[k] = 2;
		else a[k] = 1;
		return;
	}
	build(lson);
	build(rson);
	update(k);
}
void pushup(int k)
{
	update(k);
	if(k > 1)
		pushup(k >> 1);
}
signed main()
{
	cin >> k;
	cin >> s;
	reverse(s.begin(), s.end());
	build(1);
	int q;
	cin >> q;
	while(q--)
	{
		int x;
		char c;
		cin >> x >> c;
		x = s.size() - x + 1;
		s[x - 1] = c;
		pushup(x);
		cout << a[1] << endl;
	}
	return 0;
}

Love Sakurai Yamauchi forever!!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值