Codeforces Round #807 (Div. 2)A~E个人题解

Dashboard - Codeforces Round #807 (Div. 2) - Codeforces

A. Mark the Photographer

题意:

        有2\times n个人,每个人的身高设为h_i,现在要把这些人均匀的排成俩排,每排n个人,规定对于所有的i \in [1,n],后排人的身高要比前排人的身高高x,问是否能满足?

知识点:贪心

思路:

        把所有人按身高排序,前n小的依次排到前排,后n大的依次排到后排,依次判断每个位置是否满足条件,即可。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=1e5+5;
void solve(){
    int n,x;cin>>n>>x;
    int w=n;n<<=1;
    vector<int>a(n);
    for(int i=0;i<n;++i)cin>>a[i];
    sort(a.begin(),a.end());//排序
    for(int i=0;i<w;++i){
        if(a[i+w]-a[i]<x){//最大n个和最小n个依次比较
            cout<<"NO\n";//某一个不行就是NO
            return ;
        }
    }
    cout<<"YES\n";
}
int main(){
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    int _=1;cin>>_;
    while(_--){
        solve();
    }
    return 0 ;
}

B. Mark the Dust Sweeper

题意:

        有一个长度为n的数组a,现在有一种操作如下

(1)选择一对(i,j),i<j,满足\forall k,i\leq k<j,a[k]>0

(2)把a_i--

(3)把a_j++

问最少的操作次数,使得a_1=a_2=...=a_{n-1}=0

知识点:思维

思路:

        发现对于一个不是0的数字a_i来说,要让他变成0,如果他后面到n为止的数字都不是0,他只需要操作a_i次,如果后面有0,需要一次操作去补上,所以我们只要从1开始往后看,看到第一个不是0的数字,他要操作a_i次,操作次数+=a_i,往后如果有0,说明有数来补,操作次数++。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=1e5+5;
void solve(){
    int n;cin>>n;
    vector<int>a(n+1);
    for(int i=1;i<=n;++i)cin>>a[i];
    int ops=0;
    for(int i=1;i<=n;++i){
        if(a[i]){//找到第一个不是0的数字在哪里
            ops=i;
            break;
        }
    }
    if(ops==0||ops==n){//如果在n或者没有,说明不需要操作
        cout<<0<<'\n';
        return ;
    }
    ll ans=0;
    for(int i=ops;i<n;++i){
        if(a[i])ans+=a[i];//a_i有数,操作次数+=a_i
        else ans++;//a_i==0,有数字来补,额外操作一次
    }
    cout<<ans<<'\n';
}
int main(){
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    int _=1;cin>>_;
    while(_--){
        solve();
    }
    return 0 ;
}

C. Mark and His Unfinished Essay

题意:

        现在有一个字符串s,现在可以操作c次,每次操作如下

(1)在字符串s中选择一对[l,r]的子串t,把他拼接到字符串s后面形成新的s(s=s+t)

现在有q次询问,每次询问一个x,表示问s[x]=?

知识点:递归

思路:

        如果查询的x就在最开始的字符串上,我们直接输出s[x],

如果在操作一次后的字符串上,我们看位置,

如果不在新加的字符串t上,那还是在最开始的字符串上,

如果在新加的字符串t上,因为新加的字符串t是最初字符串s的子串,所以我们还是可以在最开始的字符串上找到,图示如下

查7这个位置,我们相对于绿色方框的字符串的位置在哪里?

 

 位置就是本身减去(上一个字符串长度-本次查询的左端点+1)

这样,我们大小问题就都搞清楚了,直接递归就行了。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=1e5+5;
ll dl[45],sl[45];
string s;
char Find(ll ops,ll now){
    if(now==0)return s[ops];//如果now==0,那就是原串,直接输出位置
    if(ops>dl[now-1])return Find(ops-sl[now],now-1);//如果不在上一个串的原串里,更新
    else return Find(ops,now-1);//在上一个串的原串里,直接看
}
void solve(){
    ll n,c,q;cin>>n>>c>>q;
    cin>>s;s="#"+s;//把下标改成1开始
    dl[0]=n;sl[0]=n;//初始化
    for(int i=1;i<=c;++i){
        ll l,r;cin>>l>>r;
        dl[i]=dl[i-1]+(r-l+1);//每次操作完后字符串的长度
        sl[i]=dl[i-1]-l+1;//这是不在原串里,每次下标更新的位置
    }
    while(q--){
        ll x;cin>>x;
        cout<<Find(x,c+1)<<'\n';//输出答案
    }
}
int main(){
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    int _=1;cin>>_;
    while(_--){
        solve();
    }
    return 0 ;
}

D. Mark and Lightbulbs

题意:

        现在有一个长度为n的01串s,现在有一个操作,如下

(1)选择一个i,i\in[2,n-1],并且s_{i+1}\neq s_{i-1}

(2)把满足条件的s_i异或1

现在给你一个长度为n的01串t,问最少的操作数把字符串s变成字符串t,如果不能输出-1

知识点:思维

思路:

        我们首先可以发现,i=1和i=n这俩个位置十分滴特殊,什么情况下都不可以操作,就是一开始是什么,最后也是什么,所以我们可以直接判断,如果开始这俩个位置在串s和串t就不一样,那就是输出-1。

        然后,我们如果把连续的1看成一个联通块,那么不论怎么操作,开始联通块是几个,最后联通块还是几个(条件使然),所以我们把s中1的联通块个数,和每一个联通块的左右端点记录一下(一会要用),再把t的也记录一下,如果s中1的联通块个数和t不等,那一定不能成立,输出-1.

        接下来就都是成立的情况了,发现一个联通块可以把他左边的0变成1,也可以把左边的1变成0,右边也一样,也就是说,如下

 下面这个变成上面的,

 

先左加满,再把右边多余的删掉,

现在有多次的移动,我们要让答案最小,肯定是上面第一个联通块和下面第一个联通块匹配(因为他们也交叉不了啊),如果上面第二个和下面第一个匹配,那上面第一个就自闭了。 

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=1e5+5;
struct dd{
    int l,r;
};
void solve(){
    int n;cin>>n;
    int pre=-1,prel=0;
    vector<dd>v,w;
    for(int i=1;i<=n;++i){//统计字符串s,1的联通块数量
        char o;cin>>o;
        int x=o-'0';
        if(x==pre)continue;
        else {
            if(pre==1)v.push_back({prel,i-1});
            prel=i;
            pre=x;
        }
    }
    if(pre==1)v.push_back({prel,n});
    pre=-1;prel=0;
    for(int i=1;i<=n;++i){//统计字符串t,1的联通块数量
        char o;cin>>o;
        int x=o-'0';
        if(x==pre)continue;
        else {
            if(pre==1)w.push_back({prel,i-1});
            prel=i;
            pre=x;
        }
    }
    if(pre==1)w.push_back({prel,n});
    int len=v.size(),tmp=w.size();
    if(len!=tmp)cout<<"-1\n";//如果两字符串1的联通块数量不同,那一定不行
    //如果俩字符串1位置的字符不同,也不行
    else if(len&&v[0].l!=w[0].l&&(v[0].l==1||w[0].l==1))cout<<"-1\n";
    //如果俩字符串最后位置的字符不同,也不行
    else if(len&&v[len-1].r!=w[len-1].r&&(v[len-1].r==n||w[len-1].r==n))cout<<"-1\n";
    else {
        ll ans=0;
        for(int i=0;i<len;++i){//统计操作次数
            ans+=abs(w[i].l-v[i].l);
            ans+=abs(w[i].r-v[i].r);
        }
        cout<<ans<<'\n';
    }
}
int main(){
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    int _=1;cin>>_;
    while(_--){
        solve();
    }
    return 0 ;
}

E. Mark and Professor Koro

题意:

        有一个长度为n的数组a,有一种操作如下

(1)从数组a中找到某一个数x,出现了两次以上

(2)选两个x,从数组a中删除

(3)把数x+1,放入数组a中

现在有q次询问,每次询问前会把数组a中的a[k],k\in[1,n]赋值为l,

要询问改完的数组在操作到不能操作为止的最大值

知识点:二分,线段树

思路:

        先来个比较好想的,复杂度 qlog^2n 的,开始之前,我们先抽象一下题意

一个数x出现两次,数x+1出现次数+1,经过一定的思考,这不就是个模拟二进制吗,

我们把值域看成二进制的位数,第x位的次数=2了,进1,所以第x+1位的次数++

现在有个q次询问,每次询问前会把第a[k]位次数--,再把第l位的次数++,

二进制中某一位减怎么减呢,

如果这一位是1,那很简单把他变成0就行

如果这一位是0,那要先找比他位数高的距离他最近的1的位置,把那个位置变成0,这之间位置的数字变成1.

 

我们要先找到他前面1的位置,然后区间修改(就是区间异或1)

二进制中某一位加怎么加呢,

如果这一位是0,那很简单把他变成1就行

如果这一位是1,那要先找比他位数高的距离他最近的0的位置,把那个位置变成1,这之间位置的数字变成0.

我们要先找到他前面0的位置,然后区间修改(就是区间异或1)

最后答案就是最高位的1的位置。

怎么找当前位置前和他不同的数字在哪?我们可以前缀和+二分,前缀和表示每个位置到当前位置和他不同数字的个数,这个一定是单调的,我们要二分找的这个前缀和=1的位置就行了。

现在还有问题,我们怎么维护这个前缀和,线段树即可,区间修改,区间查询就都解决了。

最后答案也是二分找的只要看最后位置到那个位置的1的个数是1就行了

(注意,因为log^2n,线段树常数又有点大,所以有亿点点卡常)

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=2e5+155;
int has[N],a[N];
#define ls (i<<1)
#define rs ((i<<1)|1)
struct tre{
    int cnt0,cnt1,lazy;//维护区间0和1的个数
}tree[N<<2];
void work_lazy(int i,int l,int r){
    if(tree[i].lazy){
        swap(tree[l].cnt1,tree[l].cnt0);
        tree[l].lazy^=1;
        swap(tree[r].cnt1,tree[r].cnt0);
        tree[r].lazy^=1;
        tree[i].lazy=0;
    }
}
void push_up(tre &nowtre,tre ltre,tre rtre){
    nowtre.cnt0=ltre.cnt0+rtre.cnt0;
    nowtre.cnt1=ltre.cnt1+rtre.cnt1;
}
int query1(int l,int r,int i,int L,int R){//0和1分开查
    if(l>R||r<L)return 0;
    if(L<=l&&r<=R){
        return tree[i].cnt1;
    }
    work_lazy(i,ls,rs);
    int mid=l+r>>1;
    return query1(l,mid,ls,L,R)+query1(mid+1,r,rs,L,R);
}
int query0(int l,int r,int i,int L,int R){//0和1分开查
    if(l>R||r<L)return 0;
    if(L<=l&&r<=R){
        return tree[i].cnt0;
    }
    work_lazy(i,ls,rs);
    int mid=l+r>>1;
    return query0(l,mid,ls,L,R)+query0(mid+1,r,rs,L,R);
}
void change(int l,int r,int i,int L,int R){//修改其实就是交换区间0和1的个数
    if(l>R||r<L)return ;
    if(L<=l&&r<=R){
        swap(tree[i].cnt0,tree[i].cnt1);
        tree[i].lazy^=1;
        return ;
    }
    work_lazy(i,ls,rs);
    int mid=l+r>>1;
    change(l,mid,ls,L,R);
    change(mid+1,r,rs,L,R);
    push_up(tree[i],tree[ls],tree[rs]);
}
void build(int l,int r,int i){
    if(l==r){
        tree[i].cnt0=(has[l]==0);
        tree[i].cnt1=(has[l]==1);
        return ;
    }
    int mid=l+r>>1;
    build(l,mid,ls);
    build(mid+1,r,rs);
    push_up(tree[i],tree[ls],tree[rs]);
}
void dec(int x){//当前位置要减
    if(query1(1,N,1,x,x))change(1,N,1,x,x);//如果是1,直接改成0
    else {
        int l=x+1,r=N-1,mid,ans=0;
        while(l<=r){
            mid=l+r>>1;
            if(query1(1,N,1,x,mid)){
                r=mid-1;
                ans=mid;
            }
            else l=mid+1;
        }
        change(1,N,1,x,ans);
    }
}
void add(int x){//当前位置要加
    if(query0(1,N,1,x,x))change(1,N,1,x,x);//如果是0,直接改成1
    else {
        int l=x+1,r=N-1,mid,ans=x;
        while(l<=r){
            mid=l+r>>1;
            if(query0(1,N,1,x,mid)){
                r=mid-1;
                ans=mid;
            }
            else l=mid+1;
        }
        change(1,N,1,x,ans);
    }
}
void solve(){
    int n,q;cin>>n>>q;
    for(int i=1;i<=n;++i){
        cin>>a[i];
        has[a[i]]++;//当前位置个数++
    }
    for(int i=1;i<N;++i){
        has[i+1]+=has[i]/2;
        has[i]%=2;//弄成二进制
    }
    build(1,N,1);
    while(q--){
        int k,w;cin>>k>>w;
        dec(a[k]);//先减
        a[k]=w;//赋值
        add(a[k]);//再加
        int l=1,r=N-1,mid,ans=0;
        while(l<=r){//二分找答案
            mid=l+r>>1;
            if(query1(1,N,1,mid,N-1)){
                l=mid+1;
                ans=mid;
            }
            else r=mid-1;
        }
        cout<<ans<<'\n';
    }
}
int main(){
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    int _=1;//cin>>_;
    while(_--){
        solve();
    }
    return 0 ;
}

其实,可以优化到qlogn,我们把在外面的二分扔进权值线段树里就行了,代码的话,我等等写一下。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值