CF教育场比赛总结四

Educational Codeforces Round 65 (Rated for Div. 2)

B:Lost Numbers(新题)
题意:
与电脑交互,可以询问电脑四个问题,格式为(? i j),电脑返回的结果是答案序列i和j位置的乘积,四个询问之后让你输出六个数的结果是什么。原来六个数是(4 , 8, 15, 16, 23, 42 )这六个数的一种排列。
解题思路:
四次询问位置1的数与位置2,3,4,5的乘积,然后计算最大公约数,那么最大公约数就是位置为1的数,其它四个数除一下就行了,但是有两个列外,当第一个数是15或者23时,他们的最大公约数是这两个数的二倍,因此需要特殊处理一下。代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=1e9+7;
const int N=5e5+10;
int n,m,k,t=4;
int gcd(int x, int y){
     if(y == 0) return x;
     if(x <y)  return gcd(y,x);
     else return gcd(y, x%y);
}
int ans[7],tem[7],sum=108;
int main() {
    while(t--){//四次询问
        if(t==3){
            printf("? 1 2\n");
        }else if(t==2){
            printf("? 1 3\n");
        }else if(t==1){
            printf("? 1 4\n");
        }else {
            printf("? 1 5\n");
        }
        fflush(stdout);
        scanf("%d",&tem[3-t]);
    }
    int t=gcd(tem[0],tem[1]);
    t=gcd(t,tem[2]);
    t=gcd(t,tem[3]);//计算gcd
    if(t==30||t==46)t/=2;//特殊处理
    ans[0]=t;sum-=t;
    for(int i=0;i<=3;i++)ans[i+1]=tem[i]/t,sum-=ans[i+1];
    ans[5]=sum;//总和减去前五个数就是第六个数
    printf("!");
    for(int i=0;i<=5;i++){
        printf(" %d",ans[i]);
    }
    printf("\n");
    return 0;
}

C:News Distribution
题意:给出n个组,每一个组内的所有成员都有联系,问最后每一个人都联系了多少人。
解题思路:
并查集的应用,先将每一个组内的成员联系到一起,然后在对每一个人使用一次并查集,最后统计在一个区间里的有多少人,输出即可。
代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=1e9+7;
const int N=5e5+10;
int n,m,k,t,ans,sum,p,q;
int read() {
    int y=0,flag=1;
    char ch=getchar();
    while( (ch>'9' || ch<'0') && ch!='-' ) ch=getchar();
    if(ch=='-') flag=-1,ch=getchar();
    while(ch>='0' && ch<='9') y=y*10+ch-'0',ch=getchar();
    return y*flag;
}
int f[N],num[N],vis[N];
int to(int x){
    if(x==f[x])return x;
    else return f[x]=to(f[x]);
}
int main() {
    n=read(),m=read();
    for(int i=1;i<=n;i++)f[i]=i;
    for(int i=1;i<=m;i++){//将一个组内的所有人联系在一起
        k=read();
        if(k>=2)p=read();
        else for(int j=1;j<=k;j++)q=read();
        for(int j=2;j<=k;j++){
            q=read();
            int a=to(p),b=to(q);
            if(a!=b){
                f[b]=a;
            }
        }
    }
    for(int i=1;i<=n;i++)if(i!=f[i]){int a=to(i),b=to(f[i]);f[a]=b;}//在对每一个人使用一次并查集
    for(int i=1;i<=n;i++)num[f[i]]++;//统计属于同一组的有多少人
    for(int i=1;i<=n;i++)cout<<num[f[i]]<<" ";
    return 0;
}

D:Bicolored RBS
题意:给出一段完全匹配的括号序列,然后你可以对每一个括号染成红色或者蓝色,使最后红色和蓝色构成的括号序列nesting depth最小。nesting depth指的是括号嵌套的层数。
解题思路:
贪心思想,若要保证nesting depth最小,所以左括号应该01交替染色,当读入右括号时,先让0的左括号匹配,在这个过程中记录左括号的数量即可。
代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=1e9+7;
const int N=5e5+10;
int n,m,k,t,ans,sum,p,q;
string s;
int main() {
    cin>>n>>s;
    for(int i=0;i<n;i++){//k为偶说明匹配的是左括号,否则为右括号
        if(s[i]=='('){
            k++;
            cout<<k%2;
        }else {
            cout<<k%2;
            k--;
        }
    }
    return 0;
}

Educational Codeforces Round 71 (Rated for Div. 2)

B:Square Filling
题意:
给出一个01矩阵,原始矩阵全是0,你可以选择要操作的矩阵一个点,将该点和该点的左下三个方向上的3个点全部变成1,问是否可以变成给定的矩阵。
解题思路:
遍历给定的矩阵,找到满足可以将矩阵四个点全部变成1的坐标,并将这四个点进行标记,最后判断给定矩阵位置上为1的点是否被标记过,若有则不可行,否则输出点的坐标。
代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=1e9+7;
const int N=5e5+10;
int n,m,k,t,a,b;
int num[60][60],ans[4000][2],vis[60][60];
int main() {
    cin.sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>num[i][j];
        }
    }
    bool is=true;
    for(int i=1;i<n;i++){
        for(int j=1;j<m;j++){
            if(num[i][j]==1){
                if(num[i][j+1]&&num[i+1][j]&&num[i+1][j+1]){//找到满足条件的点将四个点标记为1,并记录该点坐标
                    vis[i][j]=1;
                    vis[i][j+1]=1,vis[i+1][j]=1,vis[i+1][j+1]=1;
                    ans[t++][0]=i,ans[t-1][1]=j;
                }
            }
        }
    }
    for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(num[i][j]&&!vis[i][j])is=false;//判断是否还存在给定位置为1的点还未访问
    if(is){cout<<t<<endl;for(int i=0;i<t;i++)cout<<ans[i][0]<<" "<<ans[i][1]<<endl;}
    else cout<<-1<<endl;
    return 0;
}

C:Gas Pipeline
题意:
给出一段01字符串,有1的位置必须铺高度为2的管道,0的位置可以铺高度为1或者高度为2的管道,最外层每单位1的花费为a,中间竖着的每单位1花费为b,问铺完管道的最小花费是多少。
解题思路:
因为只有0的位置有选择,所以只需要判断0的位置铺高度为2的还是高度为1的就行了,分析可知,当向下降时,最外侧加上2单位,中间减少len单位长度的竖着的管道,分析前后改变费用差即可。
代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=1e9+7;
const int N=5e5+10;
ll n,m,k,t,a,b,ans,T;
string s;
int main() {
    cin.sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>T;
    while(T--){
        vector<int>v;
        cin>>n>>a>>b>>s;
        for(int i=0;i<n;i++)if(s[i]=='1')v.push_back(i);//记录s中位置为1的位置
        if(v.size()>0){
            ans=v[0]*b+(n-v[v.size()-1]-1)*b+(n+2)*a+(v[v.size()-1]-v[0]+2)*2*b;//当有1时,先按照最外侧高度为2开始铺,但是第一个1位置前和最后一个1之后的位置高度应该均为1
        }else {
            cout<<(n+1)*b+n*a<<endl;continue;//没1肯定高度都是1
        }
        for(int i=1;i<v.size();i++){
            t=v[i]-v[i-1];
            if(t<=1)continue;//两个1间距为1,说明相邻,跳过
            else{
                if(t*2*b+(t-1)*a>(t+2)*b+(t+1)*a){//否则计算该区间内由高度为2变为1之后的花费大小,若减小了则原来费用减少两者的差值
                    ans-=(b*t-2*b-2*a);
                }
            }
        }
        cout<<ans<<endl;
    }
    return 0;
}

D:Number Of Permutations
题意:
给出n正整数对,可以对整数对进行排列,问满足所有排列的第一个数非降序并且第二个数也降序的排列有多少种。
解题思路:
答案为总的排列数-(第一个数单调不减或者第二个数单调不减的排列数)+(第一个和第二个数都单调不减的排列数)。第一个数单调不减或者第二个数单调不减的方案分别排序记录相邻位置相同树的个数就行。考虑如何计算第一个数、第二个数都单调不减的排列数。我们就在按第一个数排序的基础上在按第二个数排序,注意要判断整个序列是否满足第二个数不降,如果满足,则去每一段相同的第一个数中找第二个数相同连续段。然后加上即可。
代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=998244353;
const int N=3e5+10;
ll n,m,k,t,cnt,ans;
ll fac[N],cnt1[N],cnt2[N];
struct node{
    ll x,y;
}num[N];
bool cmp(node p,node q){
    return p.x<q.x||(p.x==q.x&&p.y<q.y);
}
int main() {
    cin.sync_with_stdio(false);
    cin.tie(0);
    fac[0]=1;//计算全排列
    cin>>n;
    for(int i=1;i<=n;i++){
        fac[i]=i*fac[i-1]%mod;
        cin>>num[i].x>>num[i].y;
        cnt1[num[i].x]++;cnt2[num[i].y]++;
    }
    ans=fac[n];
    t=1;
    for(int i=1;i<=n;i++)if(cnt1[i]>=2)t*=fac[cnt1[i]],t%=mod;//计算对第一个数的降序可行的全排列有多少
    ans-=t;t=1;
    for(int i=1;i<=n;i++)if(cnt2[i]>=2)t*=fac[cnt2[i]],t%=mod;
    ans-=t;//减去两列满足降序的全排列的个数
    sort(num+1,num+1+n,cmp);
    bool is=true;
    for(int i=1;i<n;i++)if(num[i].y>num[i+1].y)is=false;//判断在第一个数降序的情况下,第二个数是否也满足降序
    ll cnt=1,lastx=0,lasty=0,tmp=1;
    if(is)//如果满足,说明有重复的
	for(int i=1;i<=n;i++){
		if(num[i].x==lastx&&num[i].y==lasty)cnt++;
		else cnt=1;
		tmp=tmp*cnt%mod;//计算相同元素区间内两边全排列的数目
		lastx=num[i].x,lasty=num[i].y;
	}
    if(is)ans=((ans+tmp)%mod+mod)%mod;
    cout<<(ans+mod*n)%mod;//防止答案为负,加上mod*n很玄学
    return 0;
}

E:XOR Guessing
题意:
人机交互,电脑初始化一个在2^14-1到1范围内的一个随机数,你可以询问2次,每一次询问100个数,电脑会随机选择其中一个数,给你这个随机数和选的数的异或值,问你这个数是多少。
解题思路:
第一组询问我们可以输出1-100,那么x与这些数字异或得到的所有结果中对于任意结果t, t在二进制下的第8-14位绝对没有问题,因为1-100只有1-7位可能有1,后面的全是0,那么 a[i] ^ x = t1 中8-13位一定就是x的8-13位。
第二组询问我们对只要保证1-7位全都位0,那么 a[i] ^ x = t2中的1-7位一定就是x的1-7位。
然后再将两个正确的二进制拼接起来,转成结果即可。
在cin和cout下好像不用加fflush(stdout)。
代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#define LL long long
using namespace std;
int a[210],ans[20],base=(1<<7),num1,num2,res;
string tobinary(LL x){
    string s="";
    while(x){
        s+=(char)(x%2+48);
        x/=2;
    }
    return s;
}
int main(){
    for(LL i=1;i<=100;i++)a[i]=i;//建立前两百个数据
    for(LL i=1;i<=100;i++)a[i+100]=i*base;
    cout<<"?";//第一次询问
    for(int i=1;i<=100;i++)cout<<" "<<a[i];
    cout<<endl;
    cin>>num1;
    cout<<"?";//第二次询问
    for(int i=101;i<=200;i++)cout<<" "<<a[i];
    cout<<endl;
    cin>>num2;
    string t1=tobinary(num1);//取第一个数二进制下第八位及后面的二进制位,就是num1/128*128
    for(int i=7;i<t1.size();i++)ans[i]=t1[i]-48;
    string t2=tobinary(num2);//取第二个数二进制下前7个二进制位,就是num2%128
    for(int i=0;i<min(7LL,(LL)t2.size());i++)ans[i]=t2[i]-48;
    for(int i=0;i<16;i++)if(ans[i])res+=((LL)1<<i);//转成结果
    cout<<"! "<<res;//res=num1/128*128+num2%128
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值