【CF题解】Codeforces Round #738 (Div. 2) A-D2

A.Mocha and Math

题意: 每次操作可以选择一个区间【l,r】可以使得改区间内a[i]变成a[i]&a[r+l-i],可以进行无数次操作,问最终得到a数组的最小值
思路: 对于一个i来说,必然存在一个区间[l,r]可以使得该数可以和任意一个j可以相互&,因此遍历一遍数组即可
代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e6+10;
int T;
int a[maxn];
int main()
{
    int n;
    cin>>T;
    while(T--){
        cin>>n;for(int i=1;i<=n;i++) cin>>a[i];
        int maxx=a[1];
        for(int i=1;i<=n;i++) maxx&=a[i];
        printf("%d\n",maxx);
    }
    return 0;
}

B.Mocha and Red and Blue

题意: 给一个字符串,包含’B’‘R’’?’,其中’?‘处可以填’B’和’R’,问如何填可以使得’BB’'RR’的个数最少
思路:
贪心尽量使得每个B和R交替

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
int T;
int a[maxn];
string s;
int main()
{
    int n;
    cin>>T;
    while(T--){
        cin>>n;
        cin>>s;
        int fir=-1;
        for(int i=0;i<s.size();i++){
            if(s[i]!='?'){fir=i;break;}
        }
        if(fir==-1){
            for(int i=0;i<s.size();i++){
                if(i%2) s[i]='B';else s[i]='R';
            }
            cout<<s<<endl;continue;
        }
        for(int i=fir-1;i>=0;i--){
            if(s[i+1]=='B') s[i]='R';
            else s[i]='B';
        }
        for(int i=fir+1;i<s.size();i++){
            if(s[i]=='?'){
                if(s[i-1]=='B') s[i]='R';else s[i]='B';
            }
        }
        cout<<s<<endl;
    }
    return 0;
}

C.Mocha and Hiking

题意: 给n个点,2n-1条边,1到n是一条链,同时给一个a数组,a[i]=0表示i到n+1有一条边,a[i]=1表示n+1到i有一条边,从任意点出发,任意点结束,求保证每个点只经过一次的路径
思路: 由于1到n是一条链,所以我们只需考虑如何能够经过n+1这个点就可以了,显然n+1这个点只能放在首末或者中间出现一次
代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
int T;
int a[maxn];
string s;
int main()
{
    int n;
    cin>>T;
    while(T--){
        cin>>n;int x;
        for(int i=1;i<=n;i++) cin>>a[i];
        int f=0;
        for(int i=1;i<=n-1;i++){
            if(a[i]==0&&a[i+1]==1)
            {
                f=i;break;
            }
        }
        if(!f) {
                if(a[n]==0) f=1;
                if(!f){
                        if(a[1]==1) f=1;
                        if(!f){printf("-1\n");continue;}
                        else {
                            printf("%d ",n+1);
                        	for(int i=1;i<=n;i++) printf("%d ",i);printf("\n");continue;
                        }
                }
                for(int i=1;i<=n+1;i++) printf("%d ",i);printf("\n");continue;
        }
        for(int i=1;i<=n;i++)
        {
            printf("%d ",i);
            if(f==i){
                printf("%d ",n+1);
            }
        }
        printf("\n");
    }
    return 0;
}

D1.Mocha and Diana (Easy Version)

题意: 给两个森林,可以进行的操作是对两个图同时加u,v这条边可以使得两个图在操作完后仍然是森林
思路: 在构建完森林以后直接n^2跑,判断是否可以连接即可
代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,ll>P;
const int maxn=1e6+10;
int n,m1,m2;
int fa1[maxn],fa2[maxn];
int _find1(int x){
    if(x==fa1[x]) return x;
    else return fa1[x]=_find1(fa1[x]);
}
int _find2(int x){
    if(x==fa2[x]) return x;
    else return fa2[x]=_find2(fa2[x]);
}
int main()
{
    cin>>n>>m1>>m2;
    vector<P> ans;
    for(int i=1;i<=n;i++) fa1[i]=fa2[i]=i;
    int u,v;
    while(m1--){
        cin>>u>>v;
        int x=_find1(u),y=_find1(v);
        if(x!=y){
            fa1[x]=y;
        }
    }
    while(m2--){
        cin>>u>>v;
        int x=_find2(u),y=_find2(v);
        if(x!=y){
            fa2[x]=y;
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=i+1;j<=n;j++){
            int x=_find1(i),y=_find1(j);
            int xx=_find2(i),yy=_find2(j);
            if(x!=y&&xx!=yy){
                fa1[x]=y;fa2[xx]=yy;
                ans.push_back(P(i,j));
            }
        }
    }
    printf("%d\n",ans.size());
    for(int i=0;i<ans.size();i++){
        printf("%d %d\n",ans[i].first,ans[i].second);
    }
    return 0;
}

D2.Mocha and Diana (Hard Version)

思路一:并查集+技巧
已知两个点能并上的前提是fa1[i]!=fa1[j]&&fa2[i]!=fa2[j]
那么我们强行让1和其他所有点满足这个条件的先连上
那么就会变成这样两个图(圆表示一个森林)
在这里插入图片描述
然后再分别找不在图1和图2的包含1的连通块把他们相连即可
原因如下:
图中i表示的是图1中找到的不在1中的连通块
j表示的是图2中找到的不在1中的连通块
显然i在图2中在1的连通块内
j在图1中也在1的连通块内
因此,只要把i和j连接起来就是答案了
在这里插入图片描述

代码:

#include<bits/stdc++.h>
#define ls k<<1
#define rs k<<1|1
#define pb push_back
using namespace std;
const int maxn=1e6+100;
const int MAXNE=2e5+100;
typedef long long ll;
typedef pair<int,int>P;
struct
{
    int f[maxn];
    int _find(int x){
        if(x==f[x]) return x;
        else return f[x]=_find(f[x]);
    }
    void _merge(int x,int y){
        int fa1=_find(x),fa2=_find(y);
        if(fa1!=fa2){
            f[fa1]=fa2;
        }
    }
}T1,T2;
int a[maxn],b[maxn],ans=0;
int main(){
    int n,m1,m2;
    scanf("%d%d%d",&n,&m1,&m2);
    for(int i=1;i<=n;i++) T1.f[i]=T2.f[i]=i;
    int x,y;
    while(m1--){
        scanf("%d%d",&x,&y);
        T1._merge(x,y);
    }
    while(m2--){
        scanf("%d%d",&x,&y);
        T2._merge(x,y);
    }
    for(int i=2;i<=n;i++){
        if(T1._find(1)!=T1._find(i)&&T2._find(1)!=T2._find(i)){
            a[++ans]=1;b[ans]=i;
            T1._merge(1,i);T2._merge(1,i);
        }
    }
    int s1=ans,s2=ans;
    for(int i=2;i<=n;i++){
        if(T1._find(1)!=T1._find(i)){
            a[++s1]=i;
            T1._merge(1,i);
        }
        if(T2._find(1)!=T2._find(i)){
            b[++s2]=i;
            T2._merge(1,i);
        }
    }
    ans=min(s1,s2);
    printf("%d\n",ans);
    for(int i=1;i<=ans;i++) printf("%d %d\n",a[i],b[i]);
    return 0;
}

思路二:启发式合并

已知两个点能并上的前提是fa1[i]!=fa1[j]&&fa2[i]!=fa2[j]
那么我们对一个i,构造一个二元组(fa1[i],fa2[i]),将其对应到二维平面上我们会发现,i和j可以连接的前提是i和j的二元组满足图中关系
在这里插入图片描述
在i和j合并后图会变成这样(也就是i和j的行列合并了)
在这里插入图片描述
由此我们可以想到用启发式合并来实现将小的行/列并到大的行/列中去
代码:

#include<bits/stdc++.h>
#define ls k<<1
#define rs k<<1|1
#define pb push_back
using namespace std;
const int maxn=1e6+100;
const int MAXNE=2e5+100;
typedef long long ll;
typedef pair<int,int>P;
int f1[maxn],f2[maxn];
int findrow(int x){
    if(x==f1[x]) return x;
    else return f1[x]=findrow(f1[x]);
}
int findcol(int x){
    if(x==f2[x]) return x;
    else return f2[x]=findcol(f2[x]);
}
void mergerow(int x,int y){
    int fa1=findrow(x),fa2=findrow(y);
    if(fa1!=fa2){
        f1[fa1]=fa2;
    }
}
void mergecol(int x,int y){
    int fa1=findcol(x),fa2=findcol(y);
    if(fa1!=fa2){
        f2[fa1]=fa2;
    }
}
map<int,int> tu[maxn];
set<int> row[maxn],col[maxn];
set<P> Set;
set<int> ::iterator it;
int main(){
    int n,m1,m2;
    scanf("%d%d%d",&n,&m1,&m2);
    for(int i=1;i<=n;i++) f1[i]=f2[i]=i;
    int x,y;
    for(int i=0;i<m1;i++){
        scanf("%d%d",&x,&y);
        mergerow(x,y);
    }
    for(int i=0;i<m2;i++){
        scanf("%d%d",&x,&y);
        mergecol(x,y);
    }
    if(m1<m2) swap(f1,f2);
    for(int i=1;i<=n;i++){
        int x=findrow(i),y=findcol(i);
        tu[x][y]=i;
        row[x].insert(y);
        col[y].insert(x);
    }
    for(int i=1;i<=n;i++){
        if(i==findrow(i)){
            Set.insert(make_pair(-row[i].size(),i));
        }
    }
    vector<P> res;
    while(Set.size()>=2){
        int x=Set.begin()->second;
        Set.erase(Set.begin());
        int y=Set.begin()->second;
        Set.erase(Set.begin());
       // printf("x = %d, y = %d\n",x,y);
        if(row[x].size()<row[y].size()){
            swap(x,y);
        }
        it=row[x].begin();
        int a=*it,b=*(row[y].begin());
        if(a==b){
            a=*(++it);
        }
        res.push_back(make_pair(tu[x][a],tu[y][b]));
        if(col[a].size()<col[b].size()) swap(a,b);
        for(it=row[y].begin();it!=row[y].end();it++){
            tu[x][*it]=tu[y][*it];
            row[x].insert(*it);
            col[*it].erase(y);
            col[*it].insert(x);
        }
        for(it=col[b].begin();it!=col[b].end();it++){
            tu[*it][a]=tu[*it][b];
            col[a].insert(*it);
            row[*it].erase(b);
            row[*it].insert(a);
        }
        Set.insert(make_pair(-row[x].size(),x));
    }
    printf("%d\n",res.size());
    for(P x:res){
        printf("%d %d\n",x.first,x.second);
    }
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值