Codeforces Round #768 (Div. 2) A ~ E

C题及以后有详细的解释,AB略过;

A

在这里插入图片描述

思路

不难想到,最大的放一边,最小的放另一边;

Code

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

typedef long long ll;

const int N = 1e5 + 10;
int a[N],b[N];
void solve(){
    int n;
    cin >> n;
    for(int i=1;i<=n;++i) cin >> a[i];
    for(int i=1;i<=n;++i) cin >> b[i];
    for(int i=1;i<=n;++i)
        if(a[i] > b[i]) swap(a[i],b[i]);
    int mx1 = 0,mx2 = 0;
    for(int i=1;i<=n;++i){
        mx1 = max(mx1,a[i]);
        mx2 = max(mx2,b[i]);
    }
    cout << mx1 * mx2 << '\n';
}

signed main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int t;
    cin >> t;
    while(t--)
        solve();
    return 0;
}

B

在这里插入图片描述

思路

不断的翻倍即可;

因为除法是整数除法,不如乘二来的直接;

因此我们可以倒过来存数;

Code

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <set>

using namespace std;

typedef long long ll;

const int N = 2e5 + 10;

int a[N];

void solve(){
    int n;
    cin >> n;
    for(int i=n;i>=1;--i)
        cin >> a[i];
    set<int> st;
    int idx = 0;
    for(int i=1;i<=n;++i){
        if(a[1] == a[i]) ++idx;
        else break;
    }
    int mx = 0;
    for(int i=1;i<=n;++i){
        if(a[i] != a[1]) mx = max(mx,i);
    }
    if(idx == n){
        cout << 0 << '\n';
        return;
    }
    int ans = 0;
    while(idx < mx){
        idx *= 2;
        for(int i=idx+1;i<=n;++i){
            if(a[1] == a[i]) ++idx;
            else break;
        }
        ++ans;
    }
    cout << ans << '\n';
}

signed main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int t;
    cin >> t;
    while(t--)
        solve();
    return 0;
}

C

在这里插入图片描述

思路

构造题;

首先我们要想如何凑出0;

比如 n = 8 n=8 n=8
那么我们让 111 & 000 111 \& 000 111&000 110 & 001 110 \& 001 110&001 101 & 010 101 \& 010 101&010以此类推;

不难发现,其实就是从两边配对到中间即可;

又因为我们 n − 1 n-1 n1这个数字,它的二进制表示是全1;

那么如果我们想凑出一个 x , 0 < x < n − 1 x,0<x<n-1 x,0xn1

我们直接拿 n − 1 & x n-1 \& x n1&x是不是就可以了;

其他数凑 0 0 0,怎么凑呢;

我们前面提到, 0 & n − 1 0 \& n-1 0&n1 1 & n − 2 1 \& n-2 1&n2以此类推即可;

现在我们的 n − 1 n-1 n1 x x x用掉了;那么假设与 x x x匹配的是 a [ x ] a[x] a[x]

我们发现,其他数没影响,还是正常匹配,即 i & a [ i ] = 0 , i ≠ x i\&a[i] = 0,i ≠ x i&a[i]=0,i=x

同时 a [ n − 1 ] = 0 , 0 a[n-1] = 0,0 a[n1]=00也是落单的;

那么我们只需要拿 0 0 0去干掉 a [ x ] a[x] a[x]即可;


现在考虑 x = n − 1 x=n-1 x=n1的情况;

首先我们拿 n − 1 & n − 2 n-1 \& n-2 n1&n2来凑出 n − 2 n-2 n2

再拿 1 1 1 n − 3 n-3 n3来凑出 1 1 1,因为 n n n是偶数, n − 3 n-3 n3必然是奇数;

我们发现此时只有 a [ n − 1 ] , a [ n − 3 ] a[n-1],a[n-3] a[n1],a[n3]落单了,其他都是正常匹配出 0 0 0

又因为 a [ n − 1 ] = 0 a[n-1]=0 a[n1]=0,那么同理 a [ n − 1 ] & a [ n − 3 ] a[n-1] \& a[n-3] a[n1]&a[n3]即可;


最后考虑-1的情况;

因为 0 ≤ k ≤ n − 1 0≤k≤n−1 0kn1,我们与运算能凑出 0 & 1 0 \& 1 0&1 2 & 3 2 \& 3 2&3 以此类推;

不难发现,只有当 n = 4 n=4 n=4的时候,所能表示的最大值为 2 2 2,即只有此刻 s u m m a x < n − 1 sum_{max} < n-1 summaxn1,其他都是满足的;

那么特判if(n == 4 && k == 3) output(-1)即可;

Code

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

typedef long long ll;

const int N = 1e6 + 10;
int a[N];
void solve(){
    int n,k;
    cin >> n >> k;
    if(n == 4 && k == 3){
        cout << -1 << '\n';
        return;
    }
    for(int i=0;i<n;++i){
        a[i] = n - i - 1;
    }
    if(k != n - 1){
        if(k == 0){
            for(int i=0;i<n/2;++i){
                cout << i << ' ' << a[i] << '\n';
            }
        }
        else{
            cout << n-1 << ' ' << k << '\n';
            cout << 0 << ' ' << a[k] << '\n';
            for(int i=0;i<n/2;++i){
                if(i != k && a[i] != k && a[i] != n-1)
                    cout << i << ' ' << a[i] << '\n';
            }
        }
    }
    else{
        cout << n-1 << ' ' << n-2 << '\n';
        cout << 1 << ' ' << n-3 << '\n';
        cout << 0 << ' ' << 2 << '\n';
        for(int i=3;i<n/2;++i){
            cout << i << ' ' << a[i] << '\n';
        }
    }
}

signed main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int t;
    cin >> t;
    while(t--)
        solve();
    return 0;
}

D

在这里插入图片描述

思路

假设所有在范围 [ x , y ] [x,y] [x,y]中的数为 i n in in,其他数为 o u t out out

肯定有 i n + o u t = n in + out = n in+out=n

对于划分出来的第 i i i个子数组,

i n i − o u t i ≥ 1 in_i - out_i≥1 iniouti1

我们一共有 k k k个这样的子数组,那么有 ∑ i = 1 k i n i − ∑ i = 1 k o u t i ≥ 1 \sum_{i=1}^kin_i - \sum_{i=1}^kout_i ≥1 i=1kinii=1kouti1

化简一下有 i n − o u t ≥ k in - out ≥ k inoutk

再和式子 i n + o u t = n in + out = n in+out=n一起搞一下;

得到 i n ≥ ⌈ k + n 2 ⌉ in ≥ \lceil \frac{k+n}{2}\rceil in2k+n

因为我们想使得 y − x y-x yx最小,那么我们期望在 [ x , y ] [x,y] [x,y]中的元素最好恰好满足题目限制;

最理想的情况有 i n = ⌈ k + n 2 ⌉ in = \lceil \frac{k+n}{2}\rceil in=2k+n

因为每个在数组 a a a中的元素,至少出现了一次;

因此在排好序以后,我们只需要去遍历长度为 ⌈ k + n 2 ⌉ \lceil \frac{k+n}{2}\rceil 2k+n的区间从中取一个 y − x y-x yx最小的即可;

这样我们就已经得到了取值区间 [ x , y ] [x,y] [x,y]


接着考虑如何构造出 k k k个子数组;

因为我们之前已经保证了合法的下界;

对于前 k − 1 k-1 k1个子数组,

如果它们恰好 i n i − o u t i = 1 , 0 < i < k in_i - out_i = 1,0<i<k iniouti=1,0ik

那么最后一个子数组肯定能满足 i n k − o u t k ≥ 1 in_k - out_k ≥ 1 inkoutk1

Code

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <vector>

using namespace std;

typedef long long ll;

const int N = 2e5 + 10;

int a[N],l,r;
int in_range(int x){
    if(x <= r && x >= l) return 1;
    return -1;
}
void solve(){
    int n,k;
    cin >> n >> k;
    vector<int> ve;
    for(int i=1;i<=n;++i){
        cin >> a[i];
        ve.push_back(a[i]);
    }
    sort(ve.begin(),ve.end());
    int mn = 1e9;
    int len = (n+k+1)/2;//(n+k)/2 向上取整
    for(int i=0;i+len-1<ve.size();++i){
        if(ve[i+len-1] - ve[i] < mn){
            //最小化 y-x
            mn = ve[i+len-1] - ve[i];
            l = ve[i],r = ve[i+len-1];
        }
    }
    cout << l << ' ' << r << '\n';
    int cnt = 0,times = 0,last = 1;
    for(int i=1;times < k-1;){
        while(cnt <= 0){
            cnt += in_range(a[i]);
            ++i;
        }
        cout << last << ' ' << i - 1 << '\n';
        last = i;
        ++times;
        cnt = 0;
    }
    cout << last << ' ' << n << '\n';
}

signed main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int t;
    cin >> t;
    while(t--)
        solve();
    return 0;
}

E

题面

在这里插入图片描述

思路

假设只有一个连续的区间,比如 [ 1 , 2 , 3 , 1 , 5 , 1 ] [1,2,3,1,5,1] [1,2,3,1,5,1],不难发现,我们只需要关注首尾的 1 1 1,中间的 1 1 1是无用的;
这样的一个连续区间,可以产生的贡献为区间长度减去首尾两个点

如果有2个区间相交,比如 [ 1 , 2 , 3 , 1 , 2 ] [1,2,3,1,2] [1,2,3,1,2],可以产生的贡献为区间长度减去三个点

以此类推,有 k k k个区间相交,那么可以产生的贡献为区间长度减去 k + 1 k + 1 k+1


l a s t ( i ) last(i) last(i)表示数字 i i i最后出现的位置;

假设当前连续的区间为 [ i , j ] [i,j] [i,j],那么产生贡献的点在 ( i , j ) (i,j) (i,j)

因为可能有相交的区间,因此我们需要扩展右端点;

也就是寻找 m a x k = i + 1 j − 1 l a s t [ a k ] max_{k=i+1}^{j-1}last[a_k] maxk=i+1j1last[ak]

直接去做是一个 O ( n 2 ) O(n^2) O(n2)的级别,我们可以用线段树来优化;

线段树的每个节点表示位置, t r [ i ] . i d x tr[i].idx tr[i].idx表示以 i i i为根子树能够映射最远的距离;

这样执行一次查询就是 O ( l o g n ) O(logn) O(logn)级别的;

Code

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

typedef long long ll;

const int N = 2e5 + 10;

int first[N],last[N],f[N];

struct Node{
    int l,r,idx;
}tr[N<<2];

#define lc (p<<1)
#define rc (p<<1|1)
void push_up(int p){
    tr[p].idx = max(tr[lc].idx,tr[rc].idx);
}
void build(int p,int l,int r){
    tr[p] = {l,r,0};
    if(l == r){
        tr[p].idx = f[l];
        return;
    }
    int mid = l + r >> 1;
    build(lc,l,mid);
    build(rc,mid+1,r);
    push_up(p);
}
//查询区间[l,r]上的索引 -> 最远的索引
int query(int p,int l,int r){
    if(tr[p].l >= l && tr[p].r <= r){
        return tr[p].idx; 
    }
    int mid = tr[p].l + tr[p].r >> 1;
    if(r <= mid) return query(lc,l,r);
    else if(l > mid) return query(rc,l,r);
    else return max(query(lc,l,mid),query(rc,mid+1,r));
}
void solve(){
    int n;
    cin >> n;
    for(int i=1,x;i<=n;++i){
        cin >> x;
        if(!first[x]) first[x] = i;
        last[x] = i;
    }
    //1≤x≤n
    for(int i=1;i<=n;++i)
        if(first[i] != last[i])
            //最早的位置 -> 最晚的位置
            f[first[i]] = last[i];
    build(1,1,n);
    int L = 1,ans = 0;
    for(;L<=n;++L){
        int R = f[L];
        if(L <= R){
           //扩展区间
           while(1){
               int qr = query(1,L,R);
               if(qr == R) break;
               R = qr;
               --ans;//i个连续区间i+1个点剩余,每扩展一次减一次即可
            }
            //区间长度 - 2,中间节点在上面已经减去了,这里只需要减去首尾节点
            ans += R - L + 1 - 2;
            L = R;
        }
    }
    cout << ans << '\n';
}

signed main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int t = 1;
    //cin >> t;
    while(t--)
        solve();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值