CF刷题记录

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


cf969

c题

  • 题目描述:
    给定整数数组,和整数a,b(可相等),可以任意选择数组元素,进行加减a或b的操作,该操作任意次。输出数组最大值与最小值差的最小值。
  • 题解
    首先±a,b等价于加减gcd(a,b),设为d。且可以确定最后的数组极差不会大于d,故将数组元素先取MODd,然后将数组升序排序,得到的数组设为c。
    考虑每个元素都可能作为最小值,首先令 c 1 c_1 c1为最小值,则极差为 c n − 1 − c 1 c_{n-1}-c_1 cn1c1,然后从 c 2 c_2 c2开始遍历数组,当 c i c_i ci为最小值,那么最大值为 c i − 1 + d c_{i-1}+d ci1+d,极差为 c i − 1 + d − c i c_{i-1}+d-c_i ci1+dci,然后更新最小值。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 2;
int n,a,b;
int gcd(int a,int b){
    return b==0 ? a : (gcd(b,a%b));
}
int c[N];
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    int t;
    cin >> t;
    while (t--) {
        cin>>n>>a>>b;
        int d=gcd(a,b);
        for(int i=0;i<n;i++){
            cin>>c[i];
            c[i]%=d;
        }
        sort(c,c+n);
        int ans=c[n-1]-c[0];
        for(int i=1;i<n;i++){
            ans=min(ans,c[i-1]+d-c[i]);
        }
        cout<<ans<<'\n';
    }
    return 0;
}

d题

  • 题目描述:
    一颗树,每个节点是1或0或?(表示未确定),对于一个叶子节点,从根节点到叶子节点形成01串,若01的数量和10的数量不同,则计一分,否则不计分。周sir和和他的小跟班玩游戏,每个人轮流将?确定为0或1,周sir需要得分越高越好,而小跟班希望越小越好,两个人都很聪明,周sir先手,输出最后的得分。
  • 题解
    我们发现一个01串是否几分只与根节点和叶子节点的值有关(若两者相同,怎不计分,反之计分),故有多种情况讨论
    我们定义几个数据,方便说明:cnt0:值为0的叶子数,cnt1:值为1的叶子数,cnt2:值为?的叶子数,cnt3:值为?的非根非叶子数。
    1.根节点确定。假设为1,那么答案为cnt0+(cnt2+1)/2
    2.根节点为?。若cnt0>cnt1,则周sir第一步令根节点为1。之后依次确定根节点,则答案为cnt0+cnt2/2,反之就不赘述了
    3.根节点为?且cnt0==cnt1,此时先确定根节点是不明智的选择,周sir只能用cnt3来拖延时间,若cnt3为奇数,则是轮到小跟班确定根节点,那么答案为cnt0+(cnt2+1)/2;若cnt3为偶,答案为cnt0+cnt2/2;
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 2;
int n,a,b;
string s;
int cnt[N];
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    int t;
    cin >> t;
    while (t--) {
        memset(cnt,0,sizeof cnt);
        cin>>n;
        for(int i=1;i<n;i++){
            cin>>a>>b;
            cnt[a]++,cnt[b]++;
        }
        cin>>s;
        int cnt0=0,cnt1=0,cnt2=0,cnt3=0;
        for(int i=1;i<n;i++){
            if(cnt[i+1]==1){
                if(s[i]=='1'){
                    cnt1++;
                }else if(s[i]=='0'){
                    cnt0++;
                }
            }
            if(s[i]=='?'){
                if(cnt[i+1]==1)cnt2++;
                else cnt3++;
            }
        }
        if(s[0]!='?'){
            if(s[0]=='1'){
                cout<<cnt0+(cnt2+1)/2<<'\n';
            }else{
                cout<<cnt1+(cnt2+1)/2<<'\n';
            }
        }else{
            if(cnt1>cnt0){
                cout<<cnt1+cnt2/2<<'\n';
            }else if(cnt1<cnt0){
                cout<<cnt0+cnt2/2<<'\n';
            }else{
                if(cnt3&1){
                    cout<<cnt1+(cnt2+1)/2<<'\n';
                }else{
                    cout<<cnt1+cnt2/2<<'\n';
                }
            }
        }
    }
    return 0;
}

e题

放弃,题解都看不懂

f题

放弃

cf967

c题

交互题

  • 题目描述:
    告诉你树有n个节点,允许你询问15n次最多,每次询问格式为"? a b",系统返回给你使d(x,a)-d(x,b)的最小的x,若有多个,返回d(x,a)最小的那个。最后输出n-1条边,以!开头。
  • 题解
    树的每一个节点都可以是根,所以不妨设1节点为根,遍历i从2~n,在1和i之间二分直到,中点等于起点,就得到了i的父节点。
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5+2;
int n,ans;
int a[N];
int b[N];
int now=0;
int main() {
    int t;
    cin >> t;
    while (t--) {
        now=0;
        cin>>n;
        for(int i=2;i<=n;i++){
            cout<<"? "<<1<<' '<<i<<'\n';
            cin>>ans;
            int u=1,v=i;
            while(ans!=u){
                u=ans;
                cout<<"? "<<u<<' '<<i<<'\n';
                cin>>ans;
            }
            a[now]=u;
            b[now]=v;
            now++;
        }
        cout<<"! ";
        for(int i=0;i<now;i++){
            cout<<a[i]<<" "<<b[i]<<" ";
        }
        cout<<'\n';
    }
    return 0;
}

d题

不敢恭维

cf965

b题

1000分的题卡住了,蚌埠住了

  • 题目描述
    给你一个由1~n的数字组成的排列a,请输出另一种排列p,使
    a i + . . . a j = = p i + . . . + p j a_i+...a_j==p_i+...+p_j ai+...aj==pi+...+pj的区间数量最少。
  • 题解
    其实就是将a全体循环向右移动一位。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 2;
int n,a[N];
int main(){
    int t;
    cin>>t;
    while(t--){
        cin>>n;
        for(int i=1;i<=n;i++)cin>>a[i];
        cout<<a[n]<<' ';
        for(int i=1;i<n;i++)cout<<a[i]<<' ';
        cout<<'\n';
    }
    return 0;
}

c题

  • 题目描述
    给定整数n,k,数组a,b。你可以使 b i b_i bi为1的 a i a_i ai增加,总共增加的大小不能大于k。求max( a i a_i ai+f( a i a_i ai)),其中f( a i a_i ai)为将 a i a_i ai去掉后的数组a’的中位数。
  • 题解
    根据贪心思想, a i a_i ai一定会选择最大的数(可能是最大的 b i b_i bi为0的数,也可能是最大的 b i b_i bi为1的数);
    分两种情况讨论,若 b i b_i bi为1,那么将k全加在 a i a_i ai上最佳。然后排序求个中位数。较为轻松。
    b i b_i bi为0,那么k就要分配给剩下的数,使得中位数最大化,我们考虑二分思想,从而得到最大化的中位数。
    最后取两种情况的max。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 2;
ll n,k,ans,cnt;
struct node{
    ll da,b;
}a[N];
bool cmp(node a,node b){
    if(a.b==b.b)return a.da>b.da;
    return a.b<b.b;
}
bool check(ll x){
    int sum=0;
    for(int i=2;i<=cnt;i++){
        if(a[i].da>=x)sum++;
    }
    ll k1=k;
    for(int i=cnt+1;i<=n;i++){
        if(a[i].da>=x){
            sum++;
        }else if(a[i].b && k1>=x-a[i].da){
            sum++;
            k1-=x-a[i].da;
        }
    }
    return sum>=(n+1)/2;
}
int main(){
    int t;
    cin>>t;
    while(t--){
        cin>>n>>k;
        ans=0;
        cnt=0;
        for(int i=1;i<=n;i++)cin>>a[i].da;
        for(int i=1;i<=n;i++){
            cin>>a[i].b;
            if(a[i].b==0)cnt++;
        }
        sort(a+1,a+n+1,cmp);
        if(cnt){
            ll l=0,r=INT_MAX;
            while(l+1<r){
                ll mid=(r+l)>>1;
                if(check(mid))l=mid;
                else r=mid;
            }
            ans=max(ans,a[1].da+l);
        }
        if(cnt!=n){
            swap(a[cnt+1],a[n]);
            sort(a+1,a+n,[](node x,node y){
                return x.da<y.da;
            });
            ans=max(ans,a[n].da+k+a[n/2].da);
        }
        cout<<ans<<'\n';
    }
    return 0;
}

d题

  • 题目描述
    两只牛,一只叫Bessie,一只叫Elsie,有n个岛,编号为1~n,有n-1条主桥(且为单向桥),即i—(i+1),(1<=i<=n-1)。
    还有m条跳跃桥(同为单向桥)。
    Bessie只能走主桥,Elsie两种桥都能走,两个人比谁先到n号岛。Bessie先走,Elsie起点为1号岛。
    当一只牛从i岛去j岛,i岛坍塌。
    输出Bessie起点分别在1,2,…n-1号岛,能否胜利,能输出1,否输出0;

  • 题解
    dp
    dp[i]的Elsie的路线可以分为两种:
    1.路线的倒数第二个岛为i-1,枚举从i-1岛出发的跳跃桥。
    2.路线的倒数第二个岛不为i-1,这种情况的最优解为dp[i-1]-1;
    若dp[i]>0,代表Bessie会输

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 2;
int n,m;
int mem[N];//记录Elsie不受阻碍到达各点的最短时间
int dp[N];//记录B牛的起点为i时Elsie能够最多领先的时间
int u,v;
int main(){
    int t;
    cin>>t;
    while(t--){
        cin>>n>>m;
        vector<vector<int>>a1(n+1);//后面连接的alternative桥
        vector<vector<int>>a2(n+1);//前面连接的~~~
        for(int i=1;i<=m;i++){
            cin>>u>>v;
            a1[u].push_back(v);
            a2[v].push_back(u);
        }
        mem[1]=0;
        for(int i=2;i<=n;i++){
            mem[i]=mem[i-1]+1;
            for(auto x:a2[i]){
                mem[i]=min(mem[i],mem[x]+1);
            }
        }
        dp[1]=-1;
        cout<<"1";
        for(int i=2;i<=n-1;i++){
            dp[i]=dp[i-1]-1;
            for(auto x:a1[i-1]){
                dp[i]=max(dp[i],x-i-mem[i-1]-1);
            }
            if(dp[i]>0)cout<<"0";
            else cout<<"1";
        }
        cout<<"\n";
    }
    return 0;
}

cf963

c题

  • 题目描述
    n个房间, a i a_i ai表示第i个房间灯亮的时间,之后k秒后灯灭,再k秒后灯亮,如此进行下去;
    输出n个房间同时亮的最早时刻,若没有,输出-1;
  • 题解
    我们发现n个房间的状态具有周期性,为2k;
    故我们对 a i a_i ai取mod(2
    k),模数一样的可看作相同房间,并进行中心化:将最晚开始亮的房间的模数调整到k,方便后续操作,将调整后的数组,记录为b。
    我们考虑最晚开始亮的房间,例如 a 1 a_1 a1,那么我们只需要考察 a 1 a_1 a1 a 1 + k a_1+k a1+k这个时间段是否有同时亮的情况,我们发现同时亮的充分必要条件为 m a x ( b i ) − m i n ( b i ) max(b_i)-min(b_i) max(bi)min(bi)<k。且最早时刻为 a i + m a x ( b i ) − k a_i+max(b_i)-k ai+max(bi)k
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 2;
ll n,k;
ll m=0;//最晚的房间
ll a[N];
ll b1,b2;//b1:最小mod值,最大mod值
int main() {
    int t;
    cin >> t;
    while (t--) {
        m=0;
        cin>>n>>k;
        b1=k+1,b2=-1;
        ll nn=2*k;
        for(int i=1;i<=n;i++){
            cin>>a[i];
            m=max(m,a[i]);
        }
        ll tmp=k-m%nn;//中心化
        for(int i=1;i<=n;i++){
            ll x=(a[i]%nn+tmp+nn)%nn;
            b1=min(b1,x);
            b2=max(b2,x);
        }
        if(b2-b1<k){
            cout<<m+b2-k<<'\n';
        }else{
            cout<<"-1\n";
        }
    }
}

d题

  • 题目描述
    给定长度为n的整数数组a,给定k。若a的长度大于k,则选择数组中k个连续的数删去,直到a的长度不大于k停止删除。
    返回所有可能性中最终数组的中位数的最大值。
  • 题解
    设最终a的长度为tmp。
    考虑二分
    每次判断最终数组大于等于(或小于)mid的数量的最大值是否大于等于tmp/2+1(或小于(tmp+1)/2)。若是,l=mid;否则,r=mid;
    dp数组
    dp[i]代表从初始数组为 a 1 a_1 a1~ a i a_i ai的最终数组……
    dp2[i]有些许不同,它要求最终数组长度严格小于k。

两种思路:

求<x的数量

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e5 + 2;
int n,k;
ll dp[N],dp2[N];
int a[N];
int ans;
int rem(int a, int b) {
    if (a % b == 0)
        return b;
    return a % b;
}
bool check(int x){
    for (int i = 0; i <= n; i++)
        dp[i] = 0;
    for (int i = 1; i <= k; i++)
        dp[i] = dp[i - 1] + (a[i] < x);
    for (int i = 1; i <= k - 1; i++)
        dp2[i] = dp2[i - 1] + (a[i] < x);
    for (int i = k; i <= n; i++) {
        dp2[i] = dp2[i - 1] + (a[i] < x);
        dp2[i] = min(dp2[i], dp2[i - k]);
    }
    for (int i = k + 1; i <= n; i++) {
        dp[i] = dp2[i - 1] + (a[i] < x);
        dp[i] = min(dp[i], dp[i - k]);
    }
    //dp[n]个<x的,rem(n,k)-dp[n]个>=x的 
    return (rem(n, k) + 1) / 2 > dp[n];
}
int main(){
    int t;
    cin>>t;
    while(t--){
        cin>>n>>k;
        for(int i=1;i<=n;i++)cin>>a[i];
        if(n<=k){
            sort(a+1,a+n+1);
            cout<<a[(n+1)/2]<<'\n';
            continue;
        }
        // check(7);
        int l=1,r=1000000001;
        while(l+1<r){
            int mid=(r+l)>>1;
            if(check(mid))l=mid;
            else r=mid;
        }
        cout<<l<<'\n';
    }
    return 0;
}

求>=x的数量

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e5 + 2;
int n,k;
ll dp[N],dp2[N];
int a[N];
int ans;
bool check(int x){
    for(int i=1;i<=k;i++){
        dp[i]=dp[i-1]+(a[i]>=x);
        dp2[i]=dp[i];
    }
    dp2[k]=0;
    for(int i=k+1;i<=n;i++){
        if(i%k==0){
            dp2[i]=dp2[i-k];
            continue;
        }
        dp2[i]=max(dp2[i-1]+(a[i]>=x),dp2[i-k]);
    }
    for(int i=k+1;i<=n;i++){
        dp[i]=max(dp2[i-1]+(a[i]>=x),dp[i-k]);
    }
    int tmp=n%k;
    if(tmp==0)tmp=k;
    return dp[n]>=((tmp)/2+1);
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    int t;
    cin>>t;
    while(t--){
        cin>>n>>k;
        for(int i=1;i<=n;i++)cin>>a[i];
        if(n<=k){
            sort(a+1,a+n+1);
            cout<<a[(n+1)/2]<<'\n';
            continue;
        }
        int l=1,r=INT_MAX;
        while(l+1<r){
            int mid=(r+l)>>1;
            if(check(mid))l=mid;
            else r=mid;
        }
        cout<<l<<'\n';
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值