Codeforces Round #878 (Div.3)(CF1840) 赛后总结与题解。

前言

很久都没有半夜打比赛了,只能说打完睡了一个好觉。(废话)

题解区

A

CF题面

题目大意

对一个字符串 a a a 进行加密后得到一个新字符串 s s s

加密规则如下:

  • 在字符串 a a a 的每个字符之后,添加任意(可能为零)数量的小写字母,与字符本身不同。
  • 在每次这样的添加之后,我们将被补充的字符添加在已添加的字符后。

给出已加密字符串 s s s,求出加密之前的字符串。

思路

依照加密规则,我们可以知道,在原字符串 a a a 的,每个字符之后,必定会存在与之相同的字符,且两个字符之间的所有字符就是被添加的地方,删去中间与相同字符即可。

code

// by mornstar
// Jun/06/2023 22:40
#include<bits/stdc++.h>
using namespace std;
int main(){
	int T;
	cin>>T;
	while(T--){
		int n;
		char last;
		string s;
		cin>>n>>s;
		for(int i=0;i<n;i++){
			if(i==0)	last=s[0];
			else if(s[i]==last){
				cout<<last;
				i++;
				last=s[i];
			}
		}
		cout<<endl;
	}
}

B

CF题面

题目大意

给定 n n n k k k,求有多少个 k k k 为的二进制数(可以有前导零)小于等于 n n n;

1 ≤ n , k ≤ 1 0 9 1\le n,k\le 10^9 1n,k109

思路

我们知道, k k k 位的二进制数可以表示 [ 0 , 2 k − 1 ] [0,2^k-1] [0,2k1] 之间的所有数。

所以当 n ≤ 2 k − 1 n\le 2^k-1 n2k1 时,区间 [ 0 , n ] [0,n] [0,n] 内的束都可以被表示出来,所以答案为 n + 1 n+1 n+1

否则就只能表示 [ 0 , 2 k − 1 ] [0,2^k-1] [0,2k1] 之间的数,答案为 2 k 2^k 2k

注意:当 k ≥ 30 k \ge 30 k30 2 k 2^k 2k 就已经超过 1 0 9 10^9 109,此时答案一定为 n + 1 n+1 n+1

code

// by mornstar
// Jun/06/2023 22:52
#include<bits/stdc++.h>
using namespace std;
int main(){
	int T;
	cin>>T;
	while(T--){
		long long n,k;
		cin>>n>>k;
		if(k>30||n<pow(2,k))	cout<<n+1<<endl;
		else	cout<<(int)pow(2,k)<<endl;
	}
}

C

CF题面

题目大意

给定 n n n k k k q q q 和一个长度为 n n n 的序列 a a a

a a a 中选出一个连续的序列,序列的长度需大于等于 k k k 并且最大值小于等于 q q q

共有多少种选法?

思路

我们可以注意到,序列中大于 q q q 的数将整个序列分成了若干个互不重叠的段。

因为要选择连续的一段,所以肯定不能越过大于 q q q 的数。

对于一个满足条件的子区间,要从中截取一个长度大于等于 k k k 的区间,有多少种方法呢?很明显,对于一个长度为 l l l 的子区间,方案总数为:
∑ i = 1 l − k + 1 i \sum_{i=1}^{l-k+1} i i=1lk+1i
而这个可以预处理出来。

code

// by mornstar
// Jun/06/2023 23:24
#include<bits/stdc++.h>
using namespace std;
long long n,k,q,a[200005],sum[200005],ans,cnt;
int main(){
	for(int i=1;i<=200005;i++)	sum[i]=sum[i-1]+i;
	int T;
	cin>>T;
	while(T--){
		ans=cnt=0;
		cin>>n>>k>>q;
		for(int i=1;i<=n;i++)	cin>>a[i];
		for(int i=1;i<=n;i++){
			if(a[i]>q){
				if(cnt>=k)	ans+=sum[cnt-k+1];
				cnt=0;
			}else{
				cnt++;
			}
		}
		if(cnt>=k)	ans+=sum[cnt-k+1];//末尾区间特判。
		cout<<ans<<endl;
	}
}

D

CF题面

题目大意

给定 n n n 和一个序列 k k k,然后选择任意三个数 a a a b b b c c c,使得 max ⁡ i = 1 n { min ⁡ { ∣ k i − a ∣ \max_{i=1}^{n}\{\min\{\left | k_i-a \right | maxi=1n{min{kia ∣ k i − b ∣ \left | k_i-b \right | kib ∣ k i − c ∣ } } \left | k_i-c \right |\}\} kic}} 最小。

输出这个最小值。

1 ≤ n ≤ 2 × 1 0 5 1\le n\le2\times 10^5 1n2×105
1 ≤ k i ≤ 1 0 9 1\le k_i\le10^9 1ki109

思路

将数组从小到大排序。

容易发现,整个序列可以分成连续三部分。在第一部分中: min ⁡ { ∣ k i − a ∣ \min\{\left | k_i-a \right | min{kia ∣ k i − b ∣ \left | k_i-b \right | kib ∣ k i − c ∣ } = ∣ k i − a ∣ \left | k_i-c \right |\}=\left | k_i-a \right | kic}=kia,另外两个部分同理。

而对于第一个区间 [ 1 , r ] [1,r] [1,r],因为其单调不递减,容易得到
max ⁡ i = 1 r { min ⁡ { ∣ k i − a ∣ , ∣ k i − b ∣ , ∣ k i − c ∣ } } = max ⁡ i = 1 r { ∣ k i − a ∣ } = max ⁡ { ∣ k 1 − a ∣ , ∣ k r − a ∣ } \begin{aligned} \max_{i=1}^{r}\{\min\{\left | k_i-a \right |,\left | k_i-b \right |,\left | k_i-c \right |\}\} &= \max_{i=1}^{r}\{\left | k_i-a \right |\} \\ &=\max\{\left | k_1-a \right |,\left | k_r-a \right |\} \end{aligned} i=1maxr{min{kia,kib,kic}}=i=1maxr{kia}=max{k1a,kra}
这时候,问题来到了如何找到一个 a a a 使得 max ⁡ { ∣ k 1 − a ∣ , ∣ k r − a ∣ } \max\{\left | k_1-a \right |,\left | k_r-a \right |\} max{k1a,kra} 最小。

很明显,当 a = ⌈ k 1 + k r 2 ⌉ a=\lceil \frac{k_1+k_r}{2} \rceil a=2k1+kr 时这个值最小,为 ⌈ k r − k 1 2 ⌉ \lceil \frac{k_r-k_1}{2} \rceil 2krk1,另外两个区间以此类推。

借此,我们可以枚举三个区间的两个交界处来确定三个区间 O ( 1 ) O(1) O(1) 计算答案。

于是,我们可以写出以下代码:

#include<bits/stdc++.h>
using namespace std;
int n,a[200005],ans;
int main(){
	int T;
	cin>>T;
	while(T--){
		bool flag=0;
		ans=INT_MAX;
		cin>>n;
		for(int i=1;i<=n;i++)	cin>>a[i];
		if(n<=3){
			cout<<0<<"\n";
			continue;
		}
		sort(a+1,a+n+1);
		for(int i=1;i<=n-2;i++){
		//i为第一个区间的右端点
			if((a[i]-a[1]+1)/2>=ans)	break;
			//如果前面的区间和已经大于目前最优答案,在枚举下去就没有必要了。
			for(int j=i+1;j<=n-1;j++){
			//j为第二个区间的右端点
				if((a[j]-a[i+1]+1)/2>=ans)	break;
				ans=min(ans,(max(max(a[i]-a[1],a[j]-a[i+1]),a[n]-a[j+1])+1)/2);
			}
		}
		cout<<ans<<"\n";
	}
}

时间复杂度 O ( n 2 ) O(n^2) O(n2),并不能通过此题。

让我们想一想如何去优化。

首先,在第一个区间右端点确定的情况下,剩下的两个区间最大值要尽可能小,那么剩余两个区间产生的贡献就要尽可能相等(想一想,为什么)。

因为第二个区间的右端点越往后, k r k_r kr 的值就越大,即第二个区间的贡献越来越大,同时第三个区间的贡献越来越小。

于是,我们可以想到一个思路——二分。

如果第二个区间的贡献比第三个区间的贡献小,则第二个区间的右端点还可以往后移。

否则第二个区间的右端点只能往前移。

AC code

// by mornstar
// Jun/07/2023 00:27
#include<bits/stdc++.h>
using namespace std;
int n,a[200005],ans;
int main(){
	int T;
	cin>>T;
	while(T--){
		ans=INT_MAX;
		cin>>n;
		for(int i=1;i<=n;i++)	cin>>a[i];
		if(n<=3){
			cout<<0<<"\n";
			continue;
		}
		sort(a+1,a+n+1);
		for(int i=1;i<=n-2;i++){
			if((a[i]-a[1]+1)/2>=ans)	break;
			//同上
			int l=i+1,r=n;
			while(l<r){
				int mid=(l+r)>>1;
				ans=min(ans,(max(max(a[i]-a[1],a[mid]-a[i+1]),a[n]-a[mid+1])+1)/2);
				if((a[mid]-a[i+1]+1)/2<=(a[n]-a[mid+1]+1)/2)	l=mid+1;
				else	r=mid;
			}
			//二分
		}
		cout<<ans<<"\n";
	}
}

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

总结区

这次对于我来说考得还蛮好,但是不要存有侥幸心理,交明知道过不了的暴力程序,否则在这里插入图片描述
吃罚时真的很痛苦。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值