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;
}