Codeforces Round 905 (Div. 3) 补题报告

题目链接

1. 比赛报告

共8题, 前3题AC, 第4题TLE, 5-7题AC, 第8题没做
本篇题解只提供前7道题

2. 题解

2.1 A. Morning

2.1.1 题目大意:

你需要输入 t t t组四位数密码, 输入设备是1234567890, 每次光标都在 1 1 1上, 你可以输入这个数字或将光标移动到相邻的数字, 问输入密码所需的最少秒数

2.1.2 题目解析:

用字符串存数, 每次要移动的距离是 a b s ( s i − s i − 1 ) abs(s_i - s_{i-1}) abs(sisi1), 0 0 0排在最后一位, 所以遇到 0 0 0时可以赋值为 10 10 10, 也就是 s i = ′ 0 ′ + 10 s_i = '0' + 10 si=0+10

2.1.3 AC代码:

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;

int t, x, a, b, c, d;
int main(){
	scanf("%d", &t);
	while(t--){
        // 我用int类型做, 但string会更方便
		scanf("%d", &x);
		a = x / 1000;
		b = x / 100 % 10;
		c = x / 10 % 10;
		d = x % 10;
		if(a == 0)	a = 10;
		if(b == 0)	b = 10;
		if(c == 0)	c = 10;
		if(d == 0)	d = 10;
		printf("%d\n", a-1 + abs(a-b) + abs(b-c) + abs(c-d) + 4);
	}
	return 0;
}

2.2 B. Chemistry

2.2.1 题目大意:

输入长度为 n n n的字符串 s s s, 问从中删除 k k k个字符并重新排列是否能形成回文字符串

2.2.2 题目解析:

如果一个字符串是回文的, 那么它里面每个字符 出现的次数是奇数 的个数小于等于 1 1 1, 每一个奇数次数的字符都可以删掉一个达到对称

可以记录每个字符出现的次数 c i c_i ci, 统计 c i c_i ci中奇数的个数 c n t cnt cnt, 如果 c n t − k < = 1 cnt - k <= 1 cntk<=1输出 Y E S YES YES, 否则输出 N O NO NO

2.2.3 AC代码:

#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
using namespace std;

int t, n, k, c[30], l, cnt;
string s;
int main(){
	scanf("%d", &t);
	while(t--){
		cnt = 0;
		memset(c, 0, sizeof(c)); 
		
		scanf("%d %d", &n, &k);
		cin >> s;
		l = s.size();
		
		for(int i = 0;i < l;i++)
			c[s[i]-'a']++;
		for(int i = 0;i < 26;i++)
			if(c[i] & 1)	cnt++;
		
		if(cnt - k <= 1)	printf("YES\n");1
		else	printf("NO\n");
	}
	return 0;
}

2.3 C. Raspberries

2.3.1 题目大意:

输入 n n n个整数组成的数组 a a a和一个数字 2 ≤ k ≤ 5 2 \le k \le 5 2k5, 可以每次使一个数字 + 1 +1 +1, 问最少的操作次数使 ( ∏ i = 1 n a i ) ∣ k (\displaystyle \prod_{i=1}^{n}{a_i}) \mid k (i=1nai)k

2.3.2 题目解析:

k k k为质数时, 可以把其中一个a_i 增加到 增加到 增加到k 的倍数 , 也就是求 的倍数, 也就是求 的倍数,也就是求\min(k - a_i % k)$

k = 4 k = 4 k=4时, 4 = 2 × 2 4 = 2 \times 2 4=2×2, 所以只要有两个数都是 2 2 2的倍数就能使乘积达到 4 4 4

可以用 o d d odd odd记录偶数个数, e v e n even even记录奇数个数, 如果 o d d ≥ 2 odd \ge 2 odd2答案是 0 0 0, 再如果 o d d = 1 odd = 1 odd=1 e v e n ≥ 1 even \ge 1 even1答案是 1 1 1, 因为把一个奇数 + 1 +1 +1就可以有 2 2 2个偶数, 再如果 e v e n ≥ 2 even \ge 2 even2答案是 2 2 2, 因为把两个奇数 + 1 +1 +1就可以得到 2 2 2个偶数

2.3.3 AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

int T, n, k;
int v[10];  // 可以按照这个代码也可以根据注释全都修改
int main(){
	scanf("%d", &T);
	while(T--){
		memset(v, 0, sizeof(v));
		scanf("%d %d", &n, &k);
		int t;
		while(n--){
			scanf("%d", &t);
			v[t % k]++;  // 这里存v[k - t % k]也可以
		}
		if(v[0])	printf("0\n");  // 这里就要改成v[k]
		else if(k == 4){
			if(v[2] >= 2)	printf("0\n");  // 要按照解析中的使用odd和even
			else if(v[3] >= 1)	printf("1\n");
			else if(v[2] == 1 && v[1] >= 1)	printf("1\n");
			else if(v[2] == 0 && v[1] >= 2)	printf("2\n");
		}
		else	for(int i = k;i >= 1;i--)  // 正序, if不用改, 输出i
					if(v[i]){
						printf("%d\n", k - i);
						break;
					}
	}
	return 0;
}

2.4 D. In Love

2.4.1 题目大意:

q q q次操作, 每次会加入或移除一个区间 ( l , r ) (l, r) (l,r), 每次操作回答: 是否有任何两个区间没有重合部分

2.4.2 当时思路:

定义一个差分数组 d i f dif dif, 每次添加区间时 d i f l + 1 , d i f r + 1 − 1 dif_l + 1, dif_{r+1} - 1 difl+1,difr+11, 反之移除区间, 用变量 c n t cnt cnt记录到现在有多少个区间

用树状数组统计前缀和 a a a, 如果有一个 a i = c n t a_i = cnt ai=cnt说明所有区间都在这一点重合 输出NO, 否则至少有两个区间没有重合部分输出YES

最后试验过树状数组会TLE, 线段树AC

2.4.3 题目解析:

查找是否有区间不重合, 可以转化为是否有任意一个 min ⁡ ( r ) < m a x ( l ) \min(r) < max(l) min(r)<max(l), 可以使用自动排序的容器, 如priority_queue, multiset不去重排序, 移除时优先队列无法快速找到元素, multiset有find函数可以使用

2.4.4 AC代码:

#include <iostream>
#include <set>
#include <cstring>
using namespace std;

int q;
multiset<int> L, R;
int main(){
	scanf("%d", &q);
	char op[10];
	int l, r;
	while(q--){
		scanf("%s", op);
		scanf("%d %d", &l, &r);
		if(op[0] == '+'){
			L.insert(l);
			R.insert(r);
		}
		else{
			L.erase(L.find(l));
			R.erase(R.find(r));
		}
		if(L.size() >= 1 && *R.begin() < *L.rbegin())
			printf("YES\n");
		else	printf("NO\n");
	}
	return 0;
}

2.5 E. Look Back

2.5.1 题目大意:

输入长度为 n n n的数组 a a a, 可以让 a i    ∗  ⁣ ⁣ =    2 a_i\; *\!\!= \;2 ai=2, 问最少几次操作可以是 a a a变成单调不递减序列

2.5.2 题目解析:

如果直接模拟最后一位最大可以达到 1 0 9 × 2 1 0 5 ≈ 1 0 31002 10^9 \times 2^{10^5} \approx 10^{31002} 109×21051031002, 所以可以用 t i t_i ti表示 a i = a i × 2 t i a_i = a_i \times 2^{t_i} ai=ai×2ti, 也就是要使 a i × 2 t i ≥ a i − 1 × 2 t i − 1 a_i \times 2^{t_i} \ge a_{i-1} \times 2^{t_{i-1}} ai×2tiai1×2ti1

我们比较 a i a_i ai a i − 1 a_{i-1} ai1, 分三步走:

  1. a i = a i − 1 a_i = a_{i-1} ai=ai1: 保持 a i a_i ai不变即可, 所以 t i = t i − 1 t_i = t_{i-1} ti=ti1
  2. a i > a i − 1 a_i > a_{i-1} ai>ai1: 为了更划算要降低 t i t_i ti, 这时 a i a_i ai a i − 1 a_{i-1} ai1差了几倍的 2 2 2, t i t_i ti就可以减几
  3. a i < a i − 1 a_i < a_{i-1} ai<ai1: 为达成目标要升高 t i t_i ti, 这时 a i a_i ai a i − 1 a_{i-1} ai1差了几倍的 2 2 2, t i t_i ti就可以加几

2.5.3 AC代码:

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
typedef long long ll;

const int N = 1e5 + 5;
ll T, n, a[N], t[N];
int main(){
	scanf("%lld", &T);
	while(T--){
		scanf("%lld", &n);
		ll ans = 0;
		for(int i = 1;i <= n;i++)
			scanf("%lld", &a[i]);
		for(int i = 2;i <= n;i++){
			if(a[i] >= a[i-1])
				t[i] = max(0ll, (ll)(t[i-1] - floor(log2(1.0 * a[i] / a[i-1]))));
			if(a[i] < a[i-1])
				t[i] = max(0ll, (ll)(t[i-1] + ceil(log2(1.0 * a[i-1] / a[i]))));
			ans += t[i];
		}
		printf("%lld\n", ans);
	}
	return 0;
}

2.6 F. You Are So Beautiful

2.6.1 题目大意:

输入长度为 n n n的数组 a a a, 求子**数组(连续)的个数, 满足当此子数组做为子序列(不连续)**出现时只能有一个, 也就是只有一种方法能选出此子数组

例如[1,5,6,1,7,2], 如果要选后3个作为一个答案会发现前面还有一个 1 1 1, 前面的 1 1 1加上后面的 7 , 2 7, 2 7,2作为子序列出现时就会重复

2.6.2 题目解析:

就像给出的例子, 一个数组要可以选择必须左端点的左边没有相同的数, 右端点同理

可以先正序预处理: 每个点 i i i前面有多少点是第一个出现的, 存入数组 c c c

再倒叙如果 a i a_i ai是从右往左数第一个数 a n s = a n s + c i ans = ans + c_i ans=ans+ci

2.6.3 AC代码:

#include <iostream>
#include <cstdio>
#include <map>
using namespace std;

const int N = 1e5 + 5;
int t, n, a[N], c[N];
map<int, bool> m;

int main(){
	scanf("%d", &t);
	while(t--){
		m.clear();
		long long ans = 0;
		scanf("%d", &n);
		for(int i = 1;i <= n;i++){
			scanf("%d", &a[i]);
			c[i] = c[i-1];
			if(!m[a[i]])	c[i]++, m[a[i]] = 1;
		}
		m.clear();
		for(int i = n;i >= 1;i--)
			if(!m[a[i]])	ans += c[i], m[a[i]] = 1;
		printf("%lld\n", ans);
	}
	return 0;
}

2.7 G1. Dances(Easy version)

2.7.1 题目大意:

有两个长度为 n n n的数组 a , b a, b a,b, a 1 = 1 a_1 = 1 a1=1, 每次操作可以从每个数组任意删除一个元素, 问每个 a i < b i a_i < b_i ai<bi需要的最小操作数

2.7.2 题目解析:

如果要删除 a a a中最小的元素, 就一定要删除 b b b中最小的元素, 可以先进行排序, 按照前面所说用双指针做

2.7.3 AC代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int N = 1e5 + 5;
int t, n, m, a[N], b[N];
int main(){
	scanf("%d", &t);
	while(t--){
		int ans = 0;
		scanf("%d %d", &n, &m);
		a[1] = 1;
		for(int i = 2;i <= n;i++)
			scanf("%d", &a[i]);
		for(int i = 1;i <= n;i++)
			scanf("%d", &b[i]);
		sort(a+1, a+n+1);
		sort(b+1, b+n+1);
		for(int i = 1, j = 1;i <= n-ans && j <= n;i++, j++)
			while(a[i] >= b[j] && j <= n)
				j++, ans++;
		printf("%d\n", ans);
	}
	return 0;
}

3. 反思

思路总是僵住, 没有对题目有好的解题思路, 平日没有给同学讲题的机会, 要经常复习且多做题

Y1.10 Dec.2.2023 Sat.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值