CF Round #535 (Div. 3)

ACM题集:https://blog.csdn.net/weixin_39778570/article/details/83187443
题目链接:https://codeforces.com/contest/1108

A
给出俩个区间[L1,R1] [L2,R2] 
输出x属于第一个区间,y属于第二个区间,并且x不等于y
保证有解 
--------------------------------------------------
如果第一个区间在左,显然答案 可以为 L1,R2
  若第二个区间在左,则答案可以为  	 R1,L2 
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
ll q,a,b,c,d;
int main(){
	cin>>q;
	while(q--){
		cin>>a>>b>>c>>d;
		if(c<a){
			cout<<b<<" "<<c<<endl;
		}else{
			cout<<a<<" "<<d<<endl;
		}
	}
	return 0;
}
B
给定一个序列,这个序列可以分为两个部分
一部分可以被x整除,一部分可以被y整除
----------------------------------------
对序列摆个序,最大的一定是一个结果x 
然后 x 的余数都去掉,留下最大的非余数,(或者之前出现过的)就是Y
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
int n,a[300];
int main(){
	cin>>n;
	fo(i,1,n)cin>>a[i];
	sort(a+1,a+1+n);
	cout<<a[n]<<" ";
	for(int i=n; i>=1; i--){
		if(a[n]%a[i]!=0){
			cout<<a[i];
			return 0;
		}else if(a[i]==a[i+1]){
			cout<<a[i];
			return 0;
		}
	}
	return 0;
}
C
字符串包含'R','G','B'三个字符
现在要修改一些字符使得每个相同的字符出现的下标相减为3的倍数
-----------------------------------------------------------
由于一共只有3个字符,所以最终结果一定是循环节
所以我们只要枚举BGR的全排列就好,然后逐一比较 
#include<bits/stdc++.h>
#define ll long long
#define rep(i,n) for(register int i=0; i<n; ++i)
using namespace std;
int n;
char a[3] = {'B','G','R'},str[3];
char s[200005];
int main(){
	scanf("%d%s",&n,s);
	int ans = n;
	do{	
	//	printf("%s\n",a);
		int t = 0;
		rep(i,n)if(a[i%3]!=s[i])t++;
		if(ans>t){
			ans = t;
			memcpy(str,a,sizeof(a));
		}
	}while(next_permutation(a,a+3));
	printf("%d\n",ans);
	rep(i,n)printf("%c",str[i%3]);
	return 0;
}
D
字符串包含'R','G','B'三个字符 
现在问最小修改多少个字符使得相邻两个字符互补相同
--------------------------------------------------
贪心解法
在发现一个字符和上个字符不相等之后
那么说明这个字符必须修改
那么我们修改该(i字符)字符,并且尽量使得下一个字符(i+1)不用修改
又i+2字符只与i+1字符和i+3字符有关,与i无关 
所以i字符的修改是最佳的 
#include<bits/stdc++.h>
using namespace std;
int n,ans;
char s[200005];
int main(){
	scanf("%d%s",&n,s);
	for(int i=1; i<n; i++){
		if(s[i]==s[i-1]){
			ans++;
			if(s[i-1]!='R'&&s[i+1]!='R')s[i]='R';
			else if(s[i-1]!='G'&&s[i+1]!='G')s[i]='G';
			else if(s[i-1]!='B'&&s[i+1]!='B')s[i]='B';			
		}
	}
	printf("%d\n%s",ans,s);
	return 0;
}
E1 && E2
给定一段数列
现在你有m个区间,[L,R] 
你可以选择对一些区间上的数进行减1操作.
问数列最大的数减去最小的数最大为多少
-----------------------------------------
E1做法
枚举两个数,mx和mi表示最大和最小
显然,对于mx来说,我们不用不减少他
因为如果区间只包含mx,那么减少了答案会更小
如果区间既包含mx又包含mi,那么减少了答案不会改变
所以我们对所有包含mi的区间都对mi进行减法
-----------------------------------------
E2数据加大,E1做法不行了
但是我们显然可以知道对于最终结果来说
我们只需要对包含mi的区间进行操作就行了,与mx完全无光
所以我们只需要枚举mi就行了,然后把相应的区间的数都进行减法操作就好
然后找出序列中的最大数和最小数就好
但是这里有个问题就是之前减过的数现在已经不在包含mi的区间中了
那我们需要把它恢复过来
具体可以是使用一个sub[][]数组和一个add[][]数组
sub[i]表示i这个数开始减少的区间,那么我们把这些区间的数都减少1 
add[i]表示i这个数结束减少的区间,那么我们把这些区间的数都加上1,复原 
// E1 easy 版 
#include<bits/stdc++.h>
#define ll long long 
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
int n,m,a[305],l[305],r[305];
int main(){
	scanf("%d%d",&n,&m);
	fo(i,1,n)scanf("%d",&a[i]);
	fo(i,1,m)scanf("%d%d",&l[i],&r[i]);
	int ans = 0,ansj=0,ansi=0;
	fo(i,1,n)fo(j,1,n){
		if(i==j)continue;
		int mi = a[j];
		fo(k,1,m){
			if(l[k]<=j && j<=r[k] && (i<l[k]||i>r[k])){ // i,j一起被减了没意义 
				mi--;
			}
		}
		if(ans<a[i]-mi){
			ans = a[i]-mi;
			ansj = j;
			ansi = i;
		}
	}
	vector<int> LR;
	fo(k,1,m){
		if(l[k]<=ansj && ansj<=r[k]&& (ansi<l[k]||ansi>r[k])){
			LR.push_back(k);
		}
	}	
	printf("%d\n",ans);
	printf("%d\n",LR.size());
	for(int i=0; i<LR.size(); ++i){
		printf("%d ",LR[i]);
	}
	return 0;
}
// Hard 版
#include<bits/stdc++.h> 
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
const int N = 100005;
int n,m,a[N],l[N],r[N],mi,mx;
vector<int> L[N],R[N];
int main(){
	scanf("%d%d",&n,&m); 
	mi=1e9,mx=-1e9;
	fo(i,1,n){
		scanf("%d",&a[i]);
		mi = min(a[i],mi);// 初始化,m可能为0 
		mx = max(a[i],mx);
	}
	fo(i,1,m){
		scanf("%d%d",&l[i],&r[i]); 
		L[l[i]].push_back(i);   // 开始 
		R[r[i]+1].push_back(i); // 结束 
	}
	int ans = mx-mi, ansi;
	// 从easy版我们可以知道,被减少的只有一个数就行
	fo(i,1,n){ // 枚举被减少的数 
		for(int x:L[i]){ // 要开始减的区间x 
			for(int k=l[x]; k<=r[x]; k++){
				a[k]--; 
			}
		}
		for(int x:R[i]){ // 减法结束的区间,复原 
			for(int k=l[x]; k<=r[x]; ++k){
				a[k]++;
			}
		} 
		// 空的就不用更新答案啦...会T... 
		if(L[i].size() || R[i].size()){
			mi=1e9,mx=-1e9;
			fo(k,1,n){
				mi = min(a[k],mi);
				mx = max(a[k],mx);
			}
			if(ans<mx-mi){
				ans = mx-mi;
				ansi=i;
			}			
		}
	} 
	vector<int>LR;
	fo(i,1,m){
		if(l[i]<=ansi && ansi<=r[i]){
			LR.push_back(i);
		}
	}
	printf("%d\n",ans);
	printf("%d\n",LR.size());
	for(int i=0; i<LR.size(); ++i){
		printf("%d%c",LR[i],i==LR.size()-1?'\n':' ');
	} 
	return 0;
}
F
给一副无向联通图
现在你可以给一些边的权重加1 (操作)
使得这幅图的最小生成树只有唯一一棵
求最小的操作次数
----------------------------------------
使用kruskal算法
对所有边进行一次排序后
对权重相同的边进行下面的处理
如果这条边的两个顶点已经合并到相同的簇里面了,
则这条边不用处理
剩下的这些边都是要处理的,要么合并要么提高 
对两个顶点不在同一个簇上的进行一次合并(合并之后就在同个簇上了嘛~)
剩下的那些是刚刚第一次合并在同个簇上之后的边,是需要进行增加权重的 
(权重一样,所以要提升1,其实合并哪两个顶点(簇)都是一样的) 
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
const int maxn = 200005;
struct node{
	int u,v,w;
	bool operator<(const node &p)const{
		return w<p.w;
	}
}e[maxn];
int n,m;
int p[maxn],wei[maxn];
int find(int x){
	return x==p[x] ? p[x] : p[x]=find(p[x]);
}
bool Union(int x, int y){
	int px = find(x);
	int py = find(y);
	if(px==py) return 0;
	if(wei[px]<wei[py])swap(px,py);
	wei[px] += wei[py];
	p[py] = px;
	return 1;
}
void solve(){
	sort(e+1,e+1+m);
	int ans = 0;
	for(int i=1,j=1; i<=m; i=j){
		while(j<=m && e[j].w==e[i].w)j++;
		int cnt = j-i; //  相同的边的数量 
		for(int k=i; k<j; k++){
			if(find(e[k].u) == find(e[k].v)){ // 已经连接过了 
				cnt--;
			}
		} 
		for(int k=i; k<j; k++){
			cnt -= Union(e[k].u,e[k].v); // 连接一次,减掉,其他的同源了之后就不减 
		}
		ans += cnt; 
	}
	cout<<ans<<endl;
} 
int main(){
	scanf("%d%d",&n,&m);
	fo(i,1,m){
		scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
	}
	fo(i,1,n)p[i]=i;
	memset(wei,1,sizeof(wei)); 
	solve();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值