您好,欢迎来到叨叨游戏网。
搜索
您的当前位置:首页Codeforces Round #878 (Div.3)(CF1840) 赛后总结与题解。

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

来源:叨叨游戏网

前言

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

题解区

A

题目大意

对一个字符串 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

题目大意

给定 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

题目大意

给定 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

题目大意

给定 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)

总结区

这次对于我来说考得还蛮好,但是不要存有侥幸心理,交明知道过不了的暴力程序,否则

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- gamedaodao.net 版权所有 湘ICP备2024080961号-6

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务