【题解】Educational Codeforces Round 91

A.Three Indices

题目描述:

原题面

题目分析:

这题就是要让我们找到三个数,要求中间的数最大。那么我们就考虑枚举中间的数是哪个,然后为了尽可能的使这个条件成立,我们显然就是要选择这个数左边以及右边的最小值,我们就预处理出来左边的最小值是哪个以及右边的最小值是哪个,然后每次判断一下,只要符合条件就输出。

代码详解:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
const int INF = 1e8+5;
int pre[MAXN],suf[MAXN],a[MAXN];
int main(){
	int t;
	cin>>t;
	while(t--){
		bool flag = false;
		memset(pre,0,sizeof(pre));
		memset(suf,0,sizeof(suf));
		int n;
		cin>>n;
		for(int i=1;i<=n; i++){
			cin>>a[i];
		}
		int now = INF;
		pre[0] = 1;
		for(int i=1; i<=n; i++){
			pre[i] = pre[i-1];
			if(a[i] < now){
				now = a[i];
				pre[i] = i;
			}
		}
		suf[n+1] = n;
		now = INF; 
		for(int i=n; i>=1; i--){
			suf[i] = suf[i+1];
			if(a[i] < now){
				now = a[i];
				suf[i] = i;
			}
		}
		for(int i=1; i<=n; i++){
			if(a[pre[i]] < a[i] && a[suf[i]] < a[i]){
				printf("YES\n");
				printf("%d %d %d\n",pre[i],i,suf[i]);
				flag = true;
				break;
			}
		}
		if(!flag)
			printf("NO\n");
	}
	return 0;
}

记得多组数据每次都要清空

B.Universal Solution

题目描述:

原题面

题目分析:

看到这个题第一个反应就是输出对应位置能打败它的那个,但是这样只能保证完全按照给定顺序可以很优,而一旦改变就可能非常差。而还有一种策略就是全部输出能打败最多的手势的手势,这样虽然某些情况不如上面的优,但是整体来看每种排列方式都至少都战胜 \(\frac{1}{3}\) 个所以整体的期望更优。

代码详解:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int cnt[5];
int main(){
	int t;
	cin>>t;
	while(t--){
		memset(cnt,0,sizeof(cnt));
		string s;
		cin>>s;
		for(int i=0; i<s.size(); i++){
			if(s[i] == 'R'){
				cnt[1]++;
			}
			else if(s[i] == 'S'){
				cnt[2]++;
			}
			else if(s[i] == 'P'){
				cnt[3]++;
			}
		}
		int mx = max(cnt[1],max(cnt[2],cnt[3]));
		if(cnt[1] == mx){
			for(int i=0; i<s.size(); i++){
				printf("P");
			}
		}
		else if(cnt[2] == mx){
			for(int i=0; i<s.size(); i++){
				printf("R");
			}
		}
		else if(cnt[3] == mx){
			for(int i=0; i<s.size(); i++){
				printf("S");
			}
		} 
		cout<<endl;
	}
	return 0;
}

C.Create The Teams

题目描述:

原题面

题目分析:

为了让组数更多,那么很显然我们要按能力值从大到小进行分组,如果当前选择的这一部分可以组成一组就组成一组,然后考虑下一组。这样肯定就是最优的策略,因为如果不是从大到小就可能会让很强的和很弱的分为一组那么就浪费了这个很强的人。

代码详解:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const long long MAXN = 1e5+5;
const long long INF = 1e9+5;
long long a[MAXN];
bool cmp(long long l,long long r){
	return l > r; 
}
int main(){
	long long t;
	cin>>t;
	while(t--){
		long long n,x;
		long long ans = 0;
		cin>>n>>x;
		for(long long i=1; i<=n; i++){
			cin>>a[i];
		} 
		sort(a+1,a+n+1,cmp);
		long long now_mn = INF,now_size = 0; 
		for(long long i=1; i<=n; i++){
			now_mn = min(now_mn,a[i]);
			now_size++;
			if(now_mn * now_size >= x){
				now_mn = INF;
				now_size = 0;
				ans++; 
			}
		}
		cout<<ans<<endl;
	}
	return 0;
}

D.Berserk And Fireball

题目描述:

原题面

题目分析:

遇到这类题目很明显应该用 \(b\) 去匹配 \(a\),因为这两个序列两两不相同,所以对于 \(b\) 中的每一个元素在 \(a\) 中只有唯一的元素可以匹配。所以就记录两个指针,每次找到可以与 \(b\) 的这个配对的就好了,那么就是如何考虑代价的问题。
假设现在考虑的是 \([l,r]\) 中的代价,这里是是指把 \(a\) 的这一段删去的代价:

  1. 我们可以将这一段全部使用火球术,但是因为可以火球术清理不完所以要先用狂暴术让他们变成 \(k\) 的倍数
  2. 可以全部使用狂暴术,但是这样做有一个条件就是 \(a_{l-1}\)\(a_{r+1}\) 大于这一段的最大值,因为这样才能将最大值去掉
  3. 如果不满足上述条件但是其实也能使用狂暴术,我们可以使用狂暴术让这一段只剩下 \(k\) 个,然后使用火球术清理掉

分析出来这一些那么无解的条件就很好想出来了:

  1. \(b\) 中未匹配结束但是 \(a\) 中已经没了
  2. \([l,r]\) 这一段不够使用一次火球术,而且不满足全部使用狂暴术的条件

代码详解:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const long long MAXN = 2e5+5;
const long long INF = 1e18+5;
long long n,m,x,k,y,a[MAXN],b[MAXN];
long long get_ans(long long l,long long r){
	if(l > r)
		return 0;
	long long mx = -1,len = r - l + 1;
	for(long long i=l; i<=r; i++){
		mx = max(mx,a[i]);
	}
	long long ans = 0;
	bool candel = false;
	if(a[l-1] > mx)	candel = true;
	if(a[r + 1] > mx)	candel = true;
	if(len < k && !candel)	return -1;
	long long h = len % k;
	ans = ans + h * y;
	len -= h;
	if(y * k >= x){
		ans += len / k * x;
	} 
	else if(candel){
		ans += len * y;	
	}
	else{
		ans += (len - k) * y + x;
	}
	return ans;
}
int main(){
	cin>>n>>m>>x>>k>>y;
	for(long long i=1; i<=n; i++){
		cin>>a[i];
	}
	for(long long i=1; i<=m; i++){
		cin>>b[i];
	}
	long long l = 1,r = 1,ans = 0;
	bool flag = false;
	while(r <= m){
		long long now_l = l;
		while(l <= n && a[l] != b[r]){
			l++;
		}
		long long tmp = get_ans(now_l,l-1);
		if(tmp == -1 || l == n + 1){
			flag = true;
			break;
		}
		ans += tmp;
		l++;r++;
	} 
	long long tmp = get_ans(l,n);//不能忘记最后的一小段的处理 
	if(tmp == -1){
		flag = true;
	}
	else{
		ans += tmp;
	}
	if(flag){
		printf("-1\n");
	}
	else{
		printf("%lld\n",ans);
	}
	return 0;
}

我们可能出现 \(b\) 全部匹配完了但是 \(a\) 却没有的情况,需要将 \(a\) 中剩下的这一部分去掉。因为无论如何都必须将这一段不足 \(k\) 的倍数的这写用狂暴术删掉所以就可以提前全删掉。

E.Merging Towers

题目描述:

原题面

题目描述:

因为每一座塔的盘从上到下都是从小到大的,所以我们每一次移动都一定可以在原有的基础上至少多匹配一个,所以我们所谓的困难值也就是大小相邻却不在同一座塔的圆盘的对数。
那么这样剩下的思路就好想了,每一次合并也就是判断某个塔里是否包含与当前塔里的圆盘的大小相邻的圆盘,也就是暴力将一座塔合并到另一座塔上。因为无论谁合并到谁上对答案的影响相同所以我们就考虑将小的合并的大的上面,也就是使用启发式合并的思想,这样就能将复杂度降到 \(O(\log n)\),也就是说每个点的被合并次数是最多 \(\log n\) 次,所以复杂度就是 \(O(n \log n)\)

代码详解:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5+5;
int place[MAXN],fa[MAXN];
vector<int> v[MAXN];
int find(int x){
	if(fa[x] == x)	return x;
	return fa[x] = find(fa[x]);
}
int main(){
	int n,m;
	cin>>n>>m;
	int ans = n-1;
	for(int i=1; i<=n; i++){
		cin>>place[i];
		fa[i] = i;
		v[place[i]].push_back(i);
	}
	for(int i=2; i<=n; i++){
		if(place[i] == place[i-1]){  //一开始就有不需要移动的 
			ans--;
		} 
	}
	cout<<ans<<endl;
	for(int i=1; i<m; i++){
		int x,y;
		cin>>x>>y;
		x = find(x); y = find(y);
		if(v[x].size() > v[y].size()){  //贪心的选择最小的合并所以就是个 log 的复杂度 
			swap(x,y);
		} 
		for(int i=0; i<v[x].size(); i++){
			int now = v[x][i];  //now 是 x 里的点 
			if(find(place[now-1]) == y)  //now 与 now-1 靠在了一起所以答案减一 
				ans--;
			if(find(place[now+1]) == y) // now 与 now+1 靠在了一起所以答案减一 
				ans--;
			v[y].push_back(now);
		}
		fa[x] = y;
		cout<<ans<<endl;
	} 
	return 0;
}
posted @ 2022-07-06 13:56  linyihdfj  阅读(39)  评论(0编辑  收藏  举报