Codeforces Round #751 (Div. 2)

本文提供了Codeforces Round #751 (Div.2)比赛中的六道题目(A-F)的解题思路及代码实现,涉及贪心算法、模拟、线段树优化DP等多种算法和技术。

Codeforces Round #751 (Div. 2)

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

A. Two Subsequences

思路

  • 贪心

因为 b b b 无所谓,因此找到最小的字符给 a a a 即可。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    int T;
    cin >> T;
    while (T -- ) {
        string str; cin >> str;
        char c = 'z';
        int id = -1;
        for (int i = 0; i < str.size(); i ++)
            if (str[i] <= c) c = str[i], id = i;
        cout << c << ' ';
        for (int i = 0; i < str.size(); i ++)
            if (i != id) cout << str[i];
        cout << '\n';
    }
    return 0;
}

B. Divine Array

思路

  • 模拟

    观察可知,操作一定不会进行很多次,因此模拟记录一下历史版本,然后直接读入输出即可。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[2010][2010], b[2010];
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    int T;
    cin >> T;
    while (T -- ) {
        cin >> n;
        for (int i = 1; i <= n; i ++) cin >> a[0][i];
        int id = 0;
        while (true) {
            vector<int> cnt(n + 1);
            for (int i = 1; i <= n; i ++) cnt[a[id][i]] ++;
            for (int i = 1; i <= n; i ++) b[i] = cnt[a[id][i]];
            bool ok = true;
            for (int i = 1; i <= n; i ++)
                if (b[i] != a[id][i]) {
                    ok = false;
                    break;
                }
            if (ok) break;
            id ++;
            for (int i = 1; i <= n; i ++) a[id][i] = b[i];                                
        }

        cin >> m;
        while (m --) {
            int x, y; cin >> x >> y;
            cout << a[min(id, y)][x] << '\n';
        }
    }
    return 0;
}

C. Array Elimination

思路

  • 贪心

    显然我们要遍历 k ∈ [ 1 , n ] k\in [1,n] k[1,n] 的每一种情况,对于这种和二进制相关的操作我们直接 观察二进制即可,操作等价将 k k k个数二进制共有的为 1 1 1的位减去,因此如果想让 k k k 成立,则对于 [ 0 , 30 ) [0,30) [0,30)的每一位 1 1 1出现的次数可以被 k k k整除才行,因此直接暴力判断即可。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 2e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    int T;
    cin >> T;
    while (T -- ) {
        vector<int> cnt(30);
        cin >> n;
        for (int i = 1; i <= n; i ++) {
            cin >> a[i];
            for (int j = 0; j < 30; j ++)
                if (a[i] >> j & 1)
                    cnt[j] ++;
        }

        vector<int> res;
        for (int i = 1; i <= n; i ++) {
            bool ok = true;
            for (int j = 0; j < 30; j ++)
                if (cnt[j] % i) {
                    ok = false;
                    break;
                }
            
            if (ok) res.push_back(i);
        }

        for (auto t : res) cout << t << ' ';
        cout << '\n';        
    }
    return 0;
}

D. Frog Traveler

题意

青蛙在 n n n 米深的井中。

对于每一个深度,有两个量 a i a_i ai b i b_i bi

a i a_i ai 表示在深度为 i i i米的时候可以往上跳的最高高度,就是说在深度为 i i i米的地方可以往上跳 [ 0 , a i ] [0, a_i] [0,ai]米。

b i b_i bi 表示在深度为 i i i米的地方时会往下滑 b i b_i bi米。

青蛙每跳一次,就会下滑一次。

请求出青蛙最少跳几次可以跳出井(深度为 0 0 0 米)。

思路

  • 线段树优化 d p dp dp

    f [ i ] f[i] f[i]:跳到 i i i米深所需的最小步数,我们考虑暴力 d p dp dp,对于任意一个 f [ i ] f[i] f[i] 可以转移到 [ i − a i , i ] [i-a_i,i] [iai,i] 这个范围一个 j j j 且再向下滑落 a j a_j aj 这个位置,暴力的话是 O ( n 2 ) O(n^2) O(n2)的。

    由于转移带有区间赋值,因此考虑用线段树优化,由于有下滑这个操作不是很好区间赋值,因此考虑我们人为的将它做了,现在将跳跃下滑 分成两部分, f [ i ] f[i] f[i]:跳到 i i i米深且未下滑所需的最小步数,最终我们的答案就是 f [ 0 ] f[0] f[0],那么怎么转移呢?

    对于 i i i 来说,先将 i i i下滑一下即到 i + b i i+b_i i+bi,然后再向上跳,这个时候就可以支持完整的区间复制了,即将 [ i + b i − a i + b i , i + b i ] [i+b_i-a_{i+b_i},i+b_i] [i+biai+bi,i+bi] f [ i ] + 1 f[i]+1 f[i]+1更新一下,由于还需要输出方案,因此线段树转移的时候记录一下即可。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 4e5 + 10, M = 2 * N, INF = 2e9, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N], b[N];
vector<int> in[N];
struct Node {
    int l, r; 
    int mx, tag;
    int to;
}tr[N << 2];
#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)
void down(int u) {
    if (tr[u].tag != INF) {
        tr[ls(u)].mx = min(tr[ls(u)].mx, tr[u].tag);
        tr[rs(u)].mx = min(tr[rs(u)].mx, tr[u].tag);
        tr[ls(u)].tag = min(tr[ls(u)].tag, tr[u].tag), tr[rs(u)].tag = min(tr[rs(u)].tag, tr[u].tag);
        if (tr[ls(u)].mx == tr[u].tag) tr[ls(u)].to = tr[u].to;
        if (tr[rs(u)].mx == tr[u].tag) tr[rs(u)].to = tr[u].to;
        tr[u].tag = INF;
    }
}
void build(int u, int l, int r) {
    tr[u] = {l, r, INF, INF, INF};
    if (l == r) return ;
    int mid = l + r >> 1;
    build(ls(u), l, mid), build(rs(u), mid + 1, r);    
}
void modify(int u, int l, int r, int x, int to) {
    if (l > r) return ;
    if (l <= tr[u].l && tr[u].r <= r) {
        tr[u].mx = min(tr[u].mx, x);
        tr[u].tag = min(tr[u].tag, x);        
        if (tr[u].mx == x) tr[u].to = to;
        return ;
    }
    down(u);
    int mid = tr[u].l + tr[u].r >> 1;
    if (l <= mid) modify(ls(u), l, r, x, to);
    if (r > mid) modify(rs(u), l, r, x, to);
}
Node query(int u, int x) {
    if (tr[u].l == tr[u].r) return tr[u];
    down(u);
    int mid = tr[u].l + tr[u].r >> 1;
    if (x <= mid) return query(u << 1, x);
    else return query(u << 1 | 1, x);
}
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i];
    for (int i = 1; i <= n; i ++) cin >> b[i];        
   
        
    build(1, 0, n);
  
    modify(1, n - a[n], n, 1, n);    
  //  cout << query(1, 2).mx << '\n';
    for (int i = n - 1; i >= 1; i --) {
        int p = i + b[i];
        int f = query(1, i).mx;
        modify(1, p - a[p], p, f + 1, i);
    }
   
    int v = query(1, 0).mx;
    if (v == INF) cout << -1 << '\n';
    else {
        cout << v << '\n';
        vector<int> res;
        int id = 0;
        while (true) {
            res.push_back(id);
            if (id == n) break;
            id = query(1, id).to;
        }
        res.pop_back();
        reverse(res.begin(), res.end());
        for (auto t : res) cout << t << ' ';
        cout << '\n';
        
    }

    return 0;
}

E. Optimal Insertion

题意

给定两个序列 $a,b$,长度分别为$n,m$。接下来将$b$中的所有元素以**任意方式**插入序列$a$ 中**任意位置**,请找出一种插入方式使结果序列中的逆序对数量最小化,并输出这个最小值。

思路

  • 贪心、分治

    首先贡献分成三类: a a a中本身存在的, b b b中本身存在的, a , b a,b a,b之间产生的。

    考虑 b b b中本身存在的,猜测为 0 0 0,即将 b b b数组按值从小到大的顺序依次放入 a a a中最优,证明:

    如果上述不是不最优,设最终放完的数组为 c c c 且长度为 n + m n + m n+m,则有 c i ∈ b , c j ∈ b , i < j c_i\in b,c_j\in b,i<j cib,cjb,i<j c i > c j c_i>c_j ci>cj, 交换 c i , c j c_i,c_j ci,cj后,对于任意 k < i , k > j k<i,k>j k<i,k>j和这两个点构成的贡献不变,对于 i < k < j i<k<j i<k<j c k c_k ck,如果 c k > c i ∣ ∣ c k < c j c_k>c_i||c_k<c_j ck>ci∣∣ck<cj 贡献不变,如果 c j < c k < c i c_j<c_k<c_i cj<ck<ci 贡献减二,因此将 b b b数组按照从小到大的顺序插入 a a a中是最优的。

    接下来考虑怎么让 a , b a,b a,b之间产生的逆序对最小,假设 b b b 依次插入的位置为 p 1 , p 2 , . . . , p m p_1,p_2,...,p_m p1,p2,...,pm p m = i p_m=i pm=i代表插到 i i i的前一个位置是最优的(CF1601C Optimal Insertion - yxh050917 的博客 - 洛谷博客 (luogu.com.cn))这个佬给了证明,则考虑分治将 b b b数组的 [ l 1 , r 1 ] [l_1,r_1] [l1,r1]插入 a a a数组的 [ l 2 , r 2 ] [l_2,r_2] [l2,r2]中,每次暴力填 l 1 + r 1 2 \frac{l_1+r_1}{2} 2l1+r1这个 b b b即可,由于 [ 1 , l 2 ) , ( r 2 , n ] [1,l_2),(r_2,n] [1,l2),(r2,n]的贡献是定值,因此将 b m i d b_{mid} bmid [ l 2 , r 2 ] [l_2,r_2] [l2,r2]这个区间里的 a a a构成最小的逆序对即可。

    最后随便暴力统计一下逆序对即可。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 2e6 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N], b[N];
vector<int> ve;
int find(int x) {
    return lower_bound(ve.begin(), ve.end(), x) - ve.begin() + 1;
}
int pre[N], suf[N];
int pos[N];
void dfs(int l1, int r1, int l2, int r2) {
    if (l1 > r1) return ;
    for (int i = l2 - 1; i <= r2 + 1; i ++) pre[i] = suf[i] = 0;
    int mid = l1 + r1 >> 1;
    for (int i = l2; i <= r2; i ++)
        pre[i] = pre[i - 1] + (a[i] > b[mid]);
    for (int i = r2; i >= l2; i --)
        suf[i] = suf[i + 1] + (a[i] < b[mid]);
    int p = l2;
    for (int i = l2; i <= r2; i ++)
        if (pre[i - 1] + suf[i] < pre[p - 1] + suf[p]) p = i;
    pos[mid] = p;
    dfs(l1, mid - 1, l2, p), dfs(mid + 1, r1, p, r2);
}
int tr[N];
int cnt;
void add(int x) {
    for (; x <= cnt; x += x & -x) tr[x] ++;
}

int sum(int x) {
    int res = 0;
    for (; x; x -= x & -x) res += tr[x];
    return res;
}
vector<int> g[N];
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    int T;
    cin >> T;
    while (T -- ) {
        cin >> n >> m;
        ve.clear();
        for (int i = 1; i <= n; i ++) cin >> a[i], ve.push_back(a[i]);
        for (int i = 1; i <= m; i ++) cin >> b[i], ve.push_back(b[i]);
        sort(b + 1, b + 1 + m);
        sort(ve.begin(), ve.end()), ve.erase(unique(ve.begin(), ve.end()), ve.end());
        dfs(1, m, 1, n + 1);
        cnt = ve.size();
        int id = m;
        for (int i = n + 1; i; i --) {
            g[i].clear();
            if (i <= n) g[i].push_back(find(a[i]));
            while (pos[id] == i) g[i].push_back(find(b[id --]));
        }

        for (int i = 1; i <= cnt; i ++) tr[i] = 0;
        LL res = 0;
        for (int i = n + 1; i; i --) {
            for (auto t : g[i])
                res += sum(t - 1), add(t);
        }

        cout << res << '\n';
    }
    return 0;
}

F. Difficult Mountain

题意

n n n 个人相约去爬山。
山的初始攀登难度为 d d d
每位登山者有两个属性:技巧 s s s和整洁度 a a a

技巧为 s s s 的登山者能登上攀登难度为 p p p的山当且仅当 p ≤ s p\le s ps

在一位整洁度为 a a a 的登山者登上攀登难度为 p p p的山后,山的攀登难度会变为 m a x ( p , a ) max(p,a) max(p,a)

请给这些登山者指定一个爬山的先后顺序,最大化登上山的人数。
如果轮到一位登山者时他能登上山,则他一定会选择登山。

思路

  • 贪心

    将初始 s i < d s_i<d si<d的均去除掉,然后按照 m a x ( s i , a i ) max(s_i,a_i) max(si,ai)从小到大排序,如果相同则按照 s i s_i si从小到大排序,可以证明按照这个顺序来登山的话是最优的。

    证明如下:

    假设到了第 i i i个人且当前山高为 d d d则有:

  1. m a x ( a i , s i ) = s i max(a_i,s_i)=s_i max(ai,si)=si,由于 d ≤ m a x ( a j , j ∈ [ 1 , i − 1 ] ) ≤ s i d\le max(a_j,j\in[1,i-1])\le s_i dmax(aj,j[1,i1])si,因此当前这个这个人一定可以登山,那么当前这个人登山是否是最优的呢?由于前面的都已经选了,因此只可能影响 j ∈ [ i + 1 , n ] j\in [i+1,n] j[i+1,n]的人的登山情况,分类来:

    1. 看如果 j ∈ [ i + 1 , n ] , s j = m a x ( s j , a j ) j\in [i+1,n],s_j=max(s_j,a_j) j[i+1,n],sj=max(sj,aj)均成立的话,由于按从小到大排的序,因此后面的一定可以选,所以没有影响,
    2. 否则一定存在一个位置 k ∈ [ i + 1 , n ] , a k = m a x ( s k , a k ) k\in [i+1,n],a_k=max(s_k,a_k) k[i+1,n],ak=max(sk,ak),那么 [ i + 1 , k − 1 ] [i+1,k-1] [i+1,k1]类比于情况 1 1 1一定可以都选,假设 d = a i > s k d=a_i>s_k d=ai>sk,则如果要想要 k k k 登山的话,至少要删掉 i i i,且 d d d 还可能会变大,因此对于这种情况选 i i i 也是较优的
  2. m a x ( a i , s i ) = a i max(a_i,s_i)=a_i max(ai,si)=ai,这个时候如果 s i > d s_i>d si>d的话也直接选,证明类似于上面

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 5e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
PII a[N];
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; i ++) cin >> a[i].x >> a[i].y;
    sort(a + 1, a + 1 + n, [&](PII a, PII b) {
        if (max(a.x, a.y) == max(b.x, b.y)) return a.x < b.x;
        return max(a.x, a.y) < max(b.x, b.y);
    });

    int res = 0;
    for (int i = 1; i <= n; i ++)
        if (m <= a[i].x) {
            m = max(m, a[i].y);
            res ++;
        }

    cout << res << '\n';


    return 0;
}
// 能登的 a 最小的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值