CF1684题解

发布时间 2023-11-30 19:40:29作者: Call_me_Eric

CF1684

Codeforces Round 792 (Div. 1 + Div. 2)

CF1684A

link

CF1684A题意

有一个用十进制表示的没有前导零的正整数 \(n\) 。Alice 和 Bob 正在用这个数玩一个游戏。Alice 先手,他们轮流进行游戏。

在她的这一轮中,Alice 应该交换这个数中的任何不同位置的两位。轮到 Bob 时,他每次都会删除这个数的末一位。当这个数只剩一位时,游戏结束。

你需要找出 Alice 用最佳方法在最后找出的最小数。

CF1684A题解

如果只有两位数就没辙了,只能是最后一位。
否则就是最小的那一位。

CF1684A代码

#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int x = 0, f = 1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
int n;
char ch[100];
signed main(){
int T = read();
while(T--){
    scanf("%s",ch + 1);
    n = strlen(ch + 1);
    if(n == 2){putchar(ch[2]);}
    else{
        int x = 10;
        for(int i = 1;i <= n;i++)x = min(x,ch[i] - '0');
        putchar(x + '0');
    }
    puts("");
}
    return 0;
}

CF1684B

link

CF1684B题意

给你三个数字 \(a,b,c(a<b<c)\),让你构造三个数字 \(x,y,z\),满足

\[x\equiv a\pmod y \]

\[y\equiv b\pmod z \]

\[z\equiv c\pmod x \]

CF1684B题解

不难发现当 \(x=a+b+c,y=b+c,z=c\) 时满足条件,证明略。

CF1684B代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
    int x = 0, f = 1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
signed main(){
int T = read();
while(T--){
    int a = read(), b = read(), c = read();
    printf("%lld %lld %lld\n",a + b + c,b + c,c);
}
    return 0;
}

CF1684C

link
当前用时:33:34.25

CF1684C题意

如果一个表格每行都单调不降,称它为好的。
给你 \(t\)\(n_i\)\(m_i\) 列的表格,对于每个表格,询问是否能通过调换某两列 (不一定不同) 使得这个表格是好的(这样的操作需要且仅能执行一次)。如果可以,输出两列的编号;不可以,输出 \(-1\)

CF1684C题解

先排序,然后看哪里不同就交换哪里,然后只能操作一次,交换完了在看下是不是正确的即可。

CF1684C代码

#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int x = 0, f = 1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 2e5 + 10;
vector<int> vec[maxn], srt[maxn];
int n, m;
void solve(){
    n = read(); m = read();
    for(int i = 1;i <= n;i++){
        vec[i].clear();vec[i].push_back(-114514);
        for(int j = 1;j <= m;j++)vec[i].push_back(read());
        srt[i] = vec[i];
        sort(srt[i].begin(),srt[i].end());
    }
    int pos0 = -1, pos1 = -1;
    for(int i = 1;i <= n;i++){
        for(int j = 1;j <= m;j++){
            if(srt[i][j] != vec[i][j]){
                if(pos0 == -1){pos0 = j;continue;}
                if(pos1 == -1){pos1 = j;break;}
            }
        }
        if(pos0 != -1 && pos1 != -1)break;
    }
    for(int i = 1;i <= n;i++){
        swap(vec[i][pos0],vec[i][pos1]);
        for(int j = 1;j <= m;j++)
            if(vec[i][j] != srt[i][j]){puts("-1");return;}
    }
    if(pos0 == -1 && pos1 == -1)pos0 = pos1 = 1;
    printf("%d %d\n",pos0, pos1);
}
signed main(){
    int T = read();
    while(T--)solve();
    return 0;
}

CF1684D

link
当前用时:47:52.82
Remote Judge 又双叒叕炸了,好似

CF1684D题意

这里有 \(n\) 个陷阱,你需要按照给出的顺序穿过这些陷阱,每个陷阱将会对你造成 \(a_i\) 的伤害

你有至多 \(k\) 次机会跳过这些陷阱,可以避免你所跳过的陷阱给你造成的伤害,不过接下来的所有陷阱都会给你多造成 \(1\) 点伤害

跳过陷阱所造成的额外伤害会叠加,如果你当前已经跳过了 \(3\) 个陷阱,接下来的陷阱给你造成的伤害将会是 \(a_i +3\)

现在需要你求出受到的最小伤害

CF1684D题解

首先,如果只考虑跳一次陷阱,这道题就很简单了。
答案是 \(\sum_{i=1}^na_i\)\(-\max_{i=1}^n(a_i-n+i)\) 对吧。
那如果可以跳很多次呢?
答案是前 \(k\) 大的 \(a_i-n+i\) 吗?
好像不对吧,举个栗子,你在 \(7,2\) 两个位置分别跳了一次,那么在 \(2\) 位置计算的答案就应该是 \(a_2-n+2+1\),因为在 \(7\) 位置是不受伤的。
将结论扩展到 \(k\) 个数,如果先不考虑后来躲开的伤害对前面的影响,那么答案就是 \(\sum_{i=1}^na_i\) 减去前 \(k\) 大的 \((a_i-n+i)\)
如果考虑后来躲开的伤害呢?
从排序第一个躲闪开始看起,它后面的 \(k-1\) 个躲闪对它的贡献是 \(1-k\),第二个同理。
然后最终答案就是 \(\sum_{i=1}^na_i\) 减去前 \(k\) 大的 \((a_i-n+i)\) 再减去 \(\frac{k\times (k-1)}{2}\)

CF1684D代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
    int x = 0, f = 1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 2e5 + 10;
int n, k;int a[maxn];
priority_queue<int,vector<int>,less<int> > que;
signed main(){
int T = read();
while(T--){
    n = read(); k = read();
    while(!que.empty())que.pop();
    int ans = 0;
    for(int i = 1;i <= n;i++){
        ans += (a[i] = read());
        que.push(a[i] - (n - i));
    }
    for(int i = 1;i <= k;i++){ans -= que.top();que.pop();}
    printf("%lld\n",ans - k * (k - 1) / 2);
}
    return 0;
}

CF1684E

link
当前用时:2:27:18.34
另:有的时候停下来想想会有意想不到的收获。

CF1684E题意

给你一个大小为n的数组a,保证数组内元素非负,你可以执行以下操作k次:

在一次操作中将数组内任意一个数字改为任何一个非负整数。

现在定义这个数组的成本为 \(DIFF(a)−MEX(a)\),其中 \(DIFF(a)\)\(a\) 数组内元素去重后的数量,\(MEX(a)\) 为数组中未出现的元素中最小的元素。
举个例子,\(MEX( { 1 , 2 , 3 } )=0\), \(MEX( { 0 , 1 , 2 , 4 , 5 } ) = 3\)

现在给你数组 \(a\),求能实现的最小成本。

CF1684E题解

这很好WA,爱来自对拍
首先想一个问题,对于一个确定的数组,答案是什么?
不难发现,如果将数组排好序之后,答案就是 \(Dif[Mex(a),INF]\)
其中 \(Dif[l,r]\) 表示将数组中值域为 \([l,r]\) 之间的数字取出得到新的数组 \(b\)\(DIFF(b)\) 的值。
如果以值域为 \(x\) 轴,以出现次数为 \(y\) 轴,那么对于每个数组中出现的数字 \(x\),作矩形 \((x,0)\to(x,cnt_x)\),然后就会发现,这玩应好像堆叠积木块。
如果称一个从 \((x,0)\to(x,k)\) 的矩形叫做一个,答案就变成了 \([Mex(a),INF]\) 的塔的数量。
如果设当前数组的 \(Mex(a)=t\)
不难想到,每次操作有两种选择:

  • \([t,INF]\) 值域范围内的某个数字变成 \(t\),对应在平面中就是取出在 \([t,INF]\) 中的一个积木块,放到 \(t\) 位置,称之为铺路。此时 \(t\gets t+1\)
  • 将任意一个数字变成已经出现的另一个数字,对应在平面上就是将某一个积木块放在其他的积木块之上,称之为堆叠

想想这两个操作的贡献:

  • 铺路:如果拿出积木块后塔少了一个(就是这个积木块自己组成了一个塔),那么造成 \(-1\) 贡献,否则贡献是 \(0\)
  • 堆叠:同铺路

这时不难发现,与其进行堆叠,不如直接铺路,因为铺路可能会扩展更多的塔联通,而堆叠没有。
这思路不就出来了,先排序,然后先 \(for\) 一遍看看能铺到哪里(就是 \(Mex(a)\) 最大是多少),然后每次从塔最矮的开始铺路。
然后就完了~~~~吗?
亿堆细节慢慢调吧。

CF1684E代码

#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int x = 0, f = 1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 1e5 + 10;
int n, k;
int a[maxn];
int buc[maxn];
signed main(){
int T = read();
while(T--){
    n = read();int t = k = read();
    for(int i = 1;i <= n;i++)a[i] = read();
    sort(a + 1,a + 1 + n);
    int rp = n, i = 1;
    a[n + 1] = 0x3f3f3f3f;a[0] = -1;
    for(i = 0;i <= rp && k > 0;i++){
        if(a[i + 1] - a[i] <= 1)continue;
        if(a[i + 1] - a[i] - 1 > k)break;
        while(a[i + 1] - a[i] > 1 && rp > i && k > 0){rp--;k--;a[i]++;}
        // if(a[i + 1] - a[i] > 1)break;
    }
    while(a[i + 1] - a[i] <= 1 && i <= n)i++;i++;
    // for(int i = 1;i <= n;i++){printf("a[%d]=%d ",i,a[i]);}
    // printf("\nrp = %d,i = %d\n",rp,i);

    int cnt = 1, l = i, mx = 0;
    if(i > rp){puts("0");continue;}
    memset(buc,0,sizeof(buc));
    for(int i = l;i <= n;i++){
        if(a[i] != a[i + 1]){buc[cnt]++;mx = max(mx,cnt);cnt = 1;}
        else{cnt++;}
    }
    i = 1;
    while(t && i <= mx){
        if(buc[i] <= t / i){t -= buc[i] * i;buc[i] = 0;}
        else{buc[i] -= t / i;t = 0;break;}
        i++;
    }
    cnt = 0;
    for(int i = 1;i <= mx;i++){cnt += buc[i];}
    printf("%d\n",cnt);
}
    return 0;
}

CF1684F

link
当前用时:已超时

CF1684F题意

给定长度为 \(n\) 的序列 \(a\),以及 \(m\) 个数对 \((l_i,r_i)\)
你可以进行下列操作至多一次:

  • 选择序列 \(a\) 的一个子段,并将其中的每个元素的值都改成任意整数。

你需要保证执行完操作之后,对于每个整数 \(i(1\leq i\leq m)\),都有 \(a[l_i,r_i]\) 中所有元素互不相同。
你需要最小化操作时选择的子段的长度,并求出这个长度的最小值。
特别的如果没有必要进行操作,答案为 \(0\)

CF1684F题解

破防,BYD 调了 \(3h\) 才调出来。
菜就多练练。
不难想到,首先将 \(m\) 个数对转化成对每个点 \(i\) 的限制。
如果设 \(lim_i\) 表示 \([lim_i,i]\) 之间不得有重复数字,那么 \(lim_i=\min_{1\le j\le m}^{i\in[l_j,r_j]}l_j\)
不难发现,这个 \(lim_i\) 满足单调不减(后面要考)。
然后分开不同的数字,将同一个数字的下标存在一起(设存在 \(vec_{a_i}\)数组中)。
然后设 \([L_i,R_i]\) 表示如果不选择 \(i\) 在所求区间中时,\([L_i,R_i]\) 中的每一个数字都要选择。
显然

L[i] = vec[a[i]][lower_bound(vec[a[i]].begin(),vec[a[i]].end(),lim[i]) - vec[a[i]].begin()];
R[i] = vec[a[i]][upper_bound(vec[a[i]].begin(),vec[a[i]].end(),i - 1) - vec[a[i]].begin() - 1];

然后坑爹的来了(也是我调了三个小时的原因)
对于以上这两样代码算出的 \(L_i,R_i\),需要进行边界判定:如果 \(lim_i=n+1\) 或者 \(L_i>R_i\)(尤其是这个),则 \(L_i=n+1,R_i=0\),即一定成立。

然后问题就变成了给你 \(n\) 个点,可以选择一个区间,这个区间中的所有点都会被选中,并且每个点要不然选择自己,要不然选择所有 \([L_i,R_i]\) 中的点,问这个区间最短是多少?
先说一个问题,就是是否存在一种情况,在其他条件满足的情况下,你选择了 \(i\) 但没选择 \(j(i<j)\),这时 \(i\) 的条件满足了,但是 \(j\) 的没满足?
显然不存在,因为前文说到 \(lim_i\) 单调不减,也就是 \(j\)\(i\) 的限制不强于 \(i\) 自己的限制,故一定满足。
并且由于 \(lim_i\) 单调不减,故 \(L_i\) 也单调不减,也就是说,如果使用双指针的话,左端点右移并不会导致右端点左移也能够成立。

所以线段树维护没有选择的所有点的 \(L\) 最小值和 \(R\) 最大值+双指针即可。
当然,如果你愿意,也可以预处理 \([1,i]\) 的最小最大值和 \([i,n]\) 的最小最大值,达到 \(O(n)\) 的时间复杂度。

CF1684F代码

// LUOGU_RID: 137133385
#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int x = 0, f = 1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 3e5 + 10;
int n, a[maxn], m;
int L[maxn], R[maxn];
int lim[maxn];
vector<int> vec[maxn];
struct Segment_Tree{
    struct node{
        int maxx, minn;
        node(int maxx = 0,int minn = 0):maxx(maxx),minn(minn){}
    }d[maxn << 2];
    node mergenode(node l,node r){return node(max(l.maxx,r.maxx),min(l.minn,r.minn));}
    void build(int l,int r,int p){
        if(l == r){d[p] = node(R[l],L[l]);return;}
        int mid = l + r >> 1;
        build(l,mid,p << 1);build(mid + 1,r,p << 1 | 1);
        d[p] = mergenode(d[p << 1],d[p << 1 | 1]);
    }
    void update(int l,int r,int pos,int p,int x,int y){
        if(l == r && l == pos){d[p] = node(y,x);return;}
        int mid = l + r >> 1;
        if(pos <= mid)update(l,mid,pos,p << 1,x,y);
        else update(mid + 1,r,pos,p << 1 | 1,x,y);
        d[p] = mergenode(d[p << 1],d[p << 1 | 1]);
    }
    node query(int l,int r,int s,int t,int p){
        if(s <= l && r <= t)return d[p];
        int mid = l + r >> 1;
        if(t <= mid)return query(l,mid,s,t,p << 1);
        if(mid < s)return query(mid + 1,r,s,t,p << 1 | 1);
        return mergenode(query(l,mid,s,t,p << 1),query(mid + 1,r,s,t,p << 1 | 1));
    }
    void update(int pos,int x,int y){update(1,n,pos,1,x,y);}
}tree;
inline bool check(int l,int r){
    Segment_Tree::node tmp = tree.query(1,n,1,n,1);
    return l <= tmp.minn && tmp.maxx <= r;
}

map<int,int> mp;int tot;

signed main(){
int T = read();
while(T--){
    n = read(); m = read();int u, v;mp.clear();tot = 0;
    for(int i = 1;i <= n;i++){
        a[i] = read(); if(!mp[a[i]])mp[a[i]] = ++tot;
        a[i] = mp[a[i]]; vec[i].clear();
        lim[i] = L[i] = R[i] = 0;
    }
    for(int i = 1;i <= m;i++){
        u = read(); v = read();
        vec[v].push_back(u);
    }
    u = n + 1;
    for(int i = n;i;i--){
        for(int j : vec[i]){u = min(u,j);}
        lim[i] = u;vec[i].clear();
    }
    for(int i = 1;i <= n;i++)vec[a[i]].push_back(i);
    for(int i = 1;i <= tot + 1;i++)vec[i].push_back(n + 1);
    for(int i = 1;i <= n;i++){
        if(lim[i] < i && i != vec[a[i]][0]){
            L[i] = vec[a[i]][lower_bound(vec[a[i]].begin(),vec[a[i]].end(),lim[i]) - vec[a[i]].begin()];
            R[i] = vec[a[i]][upper_bound(vec[a[i]].begin(),vec[a[i]].end(),i - 1) - vec[a[i]].begin() - 1];
        }
        else L[i] = n + 1;
        if(L[i] == n + 1 || R[i] < L[i])R[i] = 0, L[i] = n + 1;
        // printf("%d %d %d\n",i,L[i],R[i]);
    }
    u = 1; v = 0;tree.build(1,n,1);
    int ans = n, ll = 0, rr = 0;
    for(;u <= n;u++){
        while(v < n && !check(u, v)){v++;tree.update(v,n + 1,0);}
        if(check(u, v)){
            if(ans >= max(0,v - u + 1)){
                ans = max(0,v - u + 1);
                ll = u; rr = v;
            }
        }
        // if(a[1] == 827218149){
        //     if(u == 83119)printf("u = %d,v = %d ans = %d\n",u,v,ans);
        //     if(u == 53887)printf("u = %d,v = %d ans = %d\n",u,v,ans);
        // }
        tree.update(u,L[u],R[u]);
    }
    // if(ans == 62326)printf("l = %d, r = %d\n",ll,rr);
    printf("%d\n",ans);
    // for(int i = 3133;i <= 3140;i++)printf("a[%d]=%d\n",i,a[i]);
}
    return 0;
}

CF1684G

link

CF1684G题意

下面是一个经过部分改动的求 \(\gcd\) 的伪代码(其中 \(t\) 是一个初始为空的序列):

function Euclid(a, b):
    if a < b:
        swap(a, b)
    if b == 0:
        return a
    r = reminder from dividing a by b      (即设 r 为 a mod b)
    if r > 0:
        append r to the back of t          (即将 r 插入到 t 的尾部)
    return Euclid(b, r)

有一个由数对构成的序列 \(p\),接下来我们对 \(p\) 中每个数对都执行一次上述函数,然后把 \(t\) 重新排列并给定到输入中。
给定 \(n,m\) 和长度为 \(n\) 的序列 \(t\)
你需要构造一个长度不超过 \(2\times10^4\) 的数对序列 \(p\),满足:

  • 每个数对中的元素都是不超过 \(m\) 的正整数。
  • 根据序列 \(p\) 可以经过上述操作得到输入中给定的 \(t\)

有解输出任意一组解,无解输出 -1

CF1684G题解

身为网络流睪手的我当然是被秒啦(bushi)

不难想到,如果用 \((3t,2t)\) 这样的数对,就能构造出 \(t\) 这个数字,且不会附带出其他的数字。
所以对于 \(t_i\le \frac{m}{3}\) 的数字显然可以直接构造。
\(a\le\frac{m}{3}\) 的数 \(a\) 为小数,否则为大数
那剩下的 \((\frac{m}{3},m]\) 怎么办呢?
不难发现,如果用诸如 \((2t+a,t+a)\) 这样的数对,就能同时构造 \(t,a\) 这两个数字,如果还能保证 \(a|t\),那就只会构造出这两个数字。
但是同样发现,如果有一个数 \(t_i>\frac{m}{2}\),那一定无解,因为在这个代码中,一定是大数模小数,如果想构造出 \(t_i>\frac{m}{2}\),那就一定有 \(x=y+kt_i\),其中 \(k\in \mathbb{Z}且k\ge1\)\(y>k_i\),故 \(\min x=y+t_i>m\) 无解。
然后不难发现,如果用 \((2t+a,t+a)\) 这样的数对,来构造 \(t,a\) 这两个数字,那一定是 \(t>\frac{m}{3}\),不然自己构造自己的就好了。
因为 \(t>\frac{m}{3}\),由 \(2t+a\le m\)\(a\le\frac{m}{3}\)
一个大数匹配一个小数,二分图匹配
于是每个大数 \(t_i\) 尝试匹配一个小数 \(t_j\),满足 \(t_j|t_i\)\(2t_i+t_j\le m\)
跑二分图匹配即可。

CF1684G代码

#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int x = 0, f = 1;char  ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 1e3 + 10;
int n, m;
int a[maxn];
int mn[maxn], mx[maxn], tot1, tot2;

int match[maxn];
bool book[maxn];
bool dfs(int u){
    for(int v = 1;v <= tot1;v++){
        if(mx[u] % mn[v] == 0 && !book[v] && mx[u] * 2 + mn[v] <= m){
            book[v] = 1;
            if(!match[v] || dfs(match[v])){
                match[v] = u;
                return true;
            }
        }
    }
    return false;
}

vector<pair<int,int> > vec;

signed main(){
    n = read(); m = read();
    for(int i = 1;i <= n;i++)a[i] = read();
    sort(a + 1,a + 1 + n);
    if(a[n] > m / 2){puts("-1");return 0;}
    for(int i = 1;i <= n;i++){
        if(a[i] <= m / 3)mn[++tot1] = a[i];
        else mx[++tot2] = a[i];
    }
    if(tot1 < tot2){puts("-1");return 0;}
    for(int i = 1;i <= tot2;i++){
        for(int j = 1;j <= tot1;j++)book[j] = 0;
        if(!dfs(i)){puts("-1");return 0;}
    }
    for(int i = 1;i <= tot1;i++){
        if(!match[i])vec.push_back(make_pair(mn[i] * 3,mn[i] * 2));
        else vec.push_back(make_pair(mx[match[i]] + mx[match[i]] + mn[i],mx[match[i]] + mn[i]));
    }
    printf("%d\n",vec.size());
    for(auto i : vec)printf("%d %d\n",i.first,i.second);
    return 0;
}

CF1684H

*3400不会QWQ
菜就多练练pwp