Codeforces Round #651 (Div. 2)

58 篇文章 0 订阅
35 篇文章 1 订阅

A. Maximum GCD

题意:给定一个n,在1-n中任选两个数,求最大的gcd

思路:显然要让gcd最大,就是要让能约掉的因子最小。所以答案就是n/2,因为gcd(n/2,n/2*2)必然是最大的。

AC代码:

#include <bits/stdc++.h>
#define int long long
const int N = 4e5+10;
const int mod = 1e9+7;
using namespace std;
int n,m;
string s,ss;
int a[N],b[N];
 
signed main(){
    int t;
    cin>>t;
    while(t--){
        cin>>n;
        if(n<=3){
            cout<<1<<endl;
        }else{
            cout<<n/2<<endl;
        }
    }
    return 0;
}

B. GCD Compression

题意:给定2n个数,现在要进行操作,任选两个数,把他们的和放到另一个数组中。另一个数组的大小是n-1,所以在此之前,还要先删掉a中的两个数。 最后要使得 b 数组所有元素的 gcd 大于 1。

思路:gcd要大于1,那自然想到就简单的结果就是gcd >= 2,也就是保证b数组全是偶数就完了。奇数+奇数 = 偶数。 偶数+偶数 = 偶数。那么只要偶数的个数和奇数的个都是偶数个就好了。所以统计奇偶元素的个数就行了。 如果都是奇数呢,同时删掉一个就好了,和上次的题一样的套路。

AC代码:

#include <bits/stdc++.h>
#define int long long
const int N = 4e5+10;
const int mod = 1e9+7;
using namespace std;
int n,m;
string s,ss;
int a[N],b[N];
vector<int> v1;
vector<int> v2;
 
 
signed main(){
    int t;
    cin>>t;
    while(t--){
        cin>>n;
        v1.clear();
        v2.clear();
        int cnt = 0;
        for(int i = 0 ; i < 2*n ; i ++){
            cin>>a[i];
            cnt += a[i]%2;
            if(a[i]%2)  v1.push_back(i+1);
            else        v2.push_back(i+1);
        }
        if(cnt%2 == 0){
            if(cnt > 0) v1.pop_back(),v1.pop_back();
            else v2.pop_back(),v2.pop_back();
        }else{
            v1.pop_back(),v2.pop_back();
        }
        for(int i = 0 ; i < v1.size() ; i += 2){
            cout<<v1[i]<<" "<<v1[i+1]<<endl;
        }
        for(int i = 0 ; i < v2.size() ; i += 2){
            cout<<v2[i]<<" "<<v2[i+1]<<endl;
        }
 
    }
    return 0;
}

C. Number Game

题意:给定一个数n,只能进行两种操作,n 除以一个 奇数因子(包括自身),n 减掉 1,当n等于1时,游戏结束。

思路:显然 n = 1,的时候,后手胜(因为先手已经不能操作了)。n = 2,的时候先手胜,n = 3的时候,还是先手胜利,且n为奇数的时候都是先手胜利,n为奇数除以自身就完事了。那么当n为偶数时就有意思了。分情况讨论:

1. 没有奇数因子,就是2的幂次。那么这时候,先手只能进行减1操作。然后就变成奇数了。此时 后手胜。
2. 有奇数因子,且 偶数因子大于4!那么先手 先拿掉这个奇数因子,就变成了偶数。后手只能减1操作,这时候又变成奇数。先手拿掉,就赢了。
3. 有奇数因子,且 偶数因子只有2,这时候判断奇数因子,是否为质数。如果为质数,那么只能进行一次操作1和一次操作2,那么后手必胜,反之如果不是质数,可以多进行一次操作1,先手必胜。如:2x3x3x7,那么先手可以拿掉 3x7,后手要么减1,要么拿掉3,但是都是输。

AC代码:

#include <bits/stdc++.h>
#define int long long
const int N = 4e5+10;
const int mod = 1e9+7;
using namespace std;
int n,m;
string s,ss;
int a[N],b[N];
vector<int> v1;
vector<int> v2;
 
bool prime(int x){
    for(int i = 2 ; i*i <= x ; i ++){
        if(x%i == 0) return false;
    }
    return true;
}
 
signed main(){
    int t;
    cin>>t;
    while(t--){
        cin>>n;
        if(n == 1) cout<<"FastestFinger"<<endl;
        else if(n == 2){
            cout<<"Ashishgup"<<endl;
        }else if(n%2){
            cout<<"Ashishgup"<<endl;
        }else{
            int cnt = 0 ;
            while(n%2 == 0){
                n /= 2;
                cnt ++;
            }
            if(n == 1){
                cout<<"FastestFinger"<<endl;
            }else{
                if(cnt >= 2)
                    cout<<"Ashishgup"<<endl;
                else{
                    if(prime(n))
                        cout<<"FastestFinger"<<endl;
                    else{
                    cout<<"Ashishgup"<<endl;
 
                    }
                }
            }
        }
    }
    return 0;
}

D. Odd-Even Subsequence

题意:a数组有n个元素,现在要从中选出一个长度为k的序列S。并且计算序列的cost = min(max(s1,s3,s5,…),max(s2,s4,s6,…)),也就是 奇数位子上的最大值 和 偶数位置上的最大值 的 最小值。

思路:二分。直接二分答案。如果mid成立,也就是从a数组可以选出一个长度大于k的,奇数位置上全 不大于mid或者偶数位置上全不大于mid的。扫描两遍a数组,一次判断奇数位置,一次判断偶数位置,假设当前为奇数位置,那么奇数位置只能放不大于mid的数,如果是,长度=1,现在来到了偶数位置,偶数位置不关心他的大小,直接长度+1然后取下一个,然后判断选出来序列的长度是否 >= k

AC代码:

#include <bits/stdc++.h>
#define int long long
const int N = 4e5+10;
const int mod = 1e9+7;
using namespace std;
int n,m,k;
string s,ss;
int a[N],b[N],c[N];
vector<int> v1;
vector<int> v2;
 
bool check(int mid){
    int pos1 = 0;
    for(int i = 0 ; i < n ; i ++){
        if(pos1%2 == 0){
            if(a[i] <= mid){
                pos1 ++;
            }
        }else{
            pos1 ++;
        }
    }
    int pos2 = 0;
    for(int i = 0 ; i < n ; i ++){
        if(pos2%2 == 1){
            if(a[i] <= mid){
                pos2 ++;
            }
        }else{
            pos2 ++;
        }
    }
    return (pos1 >= k || pos2 >= k);
}
 
signed main(){
    int t = 1;
    //cin>>t;
    while(t--){
        cin>>n>>k;
        for(int i = 0 ; i < n ; i ++) cin>>a[i];
        int l = 1,r = 1e9+10;
        while(l < r){
            int mid = (l+r)/2;
            if(check(mid)){
                r = mid;
            }else{
                l = mid+1;
            }
        }
        cout<<l<<endl;
    }
    return 0;
}

E. Binary Subsequence Rotation

题意:给定一个01串,要变成目标串。可以选择任意一个子序列。然后顺时针移动一次。也就是最前面的到最后面,其实的移动到前一个元素的位置。问需要多少次。

思路:考虑贪心。首先,如果两个位置相等,那自然不用判断了。其次,选110或者001,这种也是非最优的选择,因为每次变化都有一个位置没有变,相当于浪费一次。所以显然是01交替出现。也就是只有两种序列可选 010101… 101010…,然后就遍历扫一遍,看这两种各最多需要多少个就行了。

AC代码:

#include <iostream>
#include <bits/stdc++.h>
#define int long long
#define mk make_pair
#define gcd __gcd
#define pb push_back
using namespace std;
const double eps = 1e-10;
const int mod = 1e9+7;
const int N = 1e6+7;
int n,m,k,t = 1,cas = 1;
int a[N],b[N],c[N];
int cnt1=0,cnt2=0,cnt3=0;

signed main(){
    string s1,s2;
    while(cin>>n){
        cin>>s1>>s2;
        int maxx1 = 0;
        int maxx2 = 0;
        int cnt0 = 0;
        int cnt1 = 0;
        for(int i = 0 ; i < n ; i ++){
            if(s1[i] == s2[i]) continue;
            if(s1[i] == '0'){
                if(cnt1) cnt1--;
                else cnt0++;
            }else{
                if(cnt0) cnt0--;
                else cnt1++;
            }
            maxx1 = max(maxx1,abs(cnt1));
            maxx2 = max(maxx2,abs(cnt0));
        }
        if(cnt1 || cnt0 )cout<<-1<<endl;
        else cout<<maxx1+maxx2<<endl;
    }
}

/**
**/

F. The Hidden Pair

题意:给定一个数。给定一个s和t,保证他们不一样。然后每次可以询问一个点集合。 会返回这个点集合中。dis(s,x)+dis(x,t)最小的点。也就是到他们的距离和最小。如果有多个,随机返回一个。并且告知dis(s,x)+dis(x,t) 这个值。问能否在 14(easy)/11(hard)次询问之内找出 s,t是哪两个点。

思路参考:https://blog.csdn.net/qq_45458915/article/details/106909780

思路:第一次询问。因为啥信息也没有。只能询问所有的点。这样就可以得到一个在 s-t 的最短路径上的某一个点,并且获得了 len = dis(s,t)。然后以这个点rt为根,dfs把到根距离相等的点放到同一堆。然后需要找出。距离根节点深度最大的并且到 dis(s,x)+dis(x,t) 距离等于 len 的点。就是这个过程。当然不可能遍历深度。 答案复杂度显然和log有关。 所以可以二分深度。 直到找到一个深度最大的,距离为len 的点。 那么这个点,必然是s或者t中的某一个。 然后以这个点为根。建树。然后再询问一次,找到深度为 len 并且距离为len 的点。就是另一个点了。easy版的 可以询问14次。 二分的起点设为l=1,r=n,就能过。hard版少几次。因为第一次和最后一次查询省不掉。只能优化二分的过程。对于左边界。设为 (len+1)/ 2。为什么呢,因为 len是s 到 t 的距离。rt又是 s,t之间的点。那么再去找距离rt最远 而到s和t距离和等于len 的点时,这个最大深度,必然不可能小于(len+1)/ 2啊。 也就是整条路径被rt切成两段了。而找的是更长的那条。所以左端点就优化了。 对于右端点,r不可能大于 min(len,max_dep)。dfs 的时候记录 max_dep,就行了。 因为不可能找到大于len 的点去。 那不显然不成立。 同时距离根节点最远的也就是max_dep,这样右端点就优化了。可以过hard。

AC代码:

#include <iostream>
#include <bits/stdc++.h>
#define int long long
#define mk make_pair
#define gcd __gcd
#define pb push_back
using namespace std;
const double eps = 1e-10;
//const int mod = 1e9+7;
const int mod = 998244353 ;
const int N = 3e5+7;
int n,m,k,t = 1,cas = 1;
int a[N],b[N];
int maxx = 0 ;
vector<int> edge[N];
vector<int> depp[N];

void dfs(int pos,int dep,int fa){
    maxx = max(maxx,dep);
    depp[dep].pb(pos);
    for(int i = 0 ; i < edge[pos].size() ; i ++){
        int to = edge[pos][i];
        if( to != fa){
            dfs(to,dep+1,pos);
        }
    }
}

pair<int,int> ask(vector<int> vt){
    cout<<"? "<<vt.size()<<" ";
    for(int i = 0 ; i < vt.size() ; i ++){
        cout<<vt[i]<<" ";
    }
    cout<<endl;
    cout.flush();
    int rt,len; cin>>rt>>len;
    return {rt,len};
}





signed main(){
    cin>>t;
    while(t--){
        maxx = 0;
        cin>>n;
        for(int i = 1 ; i <= n ; i ++) depp[i].clear(),edge[i].clear();
        for(int i = 0 ; i < n-1 ; i ++){
            int x,y;
            cin>>x>>y;
            edge[x].pb(y);
            edge[y].pb(x);
        }
        cout<<"? "<<n<<" ";
        for(int i = 1 ; i <= n ; i ++) cout<<i<<" ";
        cout<<endl;
        cout.flush();
        int rt,len;cin>>rt>>len;
        dfs(rt,0,-1);
        int l = (len+1)/2, r = min(maxx,len);
        int new_rt;
        while(l <= r){
            int mid = (l+r)>>1;
            pair<int,int> tmp = ask(depp[mid]);
            if(tmp.second == len){
                new_rt = tmp.first;
                l = mid+1;
            }else{
                r = mid-1;
            }
        }
        for(int i = 1 ; i <= n ; i ++) depp[i].clear();
        dfs(new_rt,0,-1);
        pair<int,int> res = ask(depp[len]);
        cout<<"! "<<res.first<<" "<<new_rt<<endl;
        cout.flush();
        string s;
        cin>>s;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值