Codeforces Round #807 (Div. 2) A-E

Codeforces Round #807 (Div. 2)

https://codeforces.com/contest/1705

A. Mark the Photographer

排个序,然后把数组砍成两半,看看每个位置上的对应差值是否符合

#include <bits/stdc++.h>

using namespace std;
int a[205];

void solve () {
    int n, x;
    cin >> n >> x;
    for (int i = 0; i < 2*n; i ++)
        cin >> a[i];
    sort (a, a + 2*n);
    for (int i = 0; i < n; i ++) {
        if (a[i+n] - a[i] < x) {
            cout << "NO\n";
            return ;
        }
    }
    cout << "YES\n";
}

int main () {
    int t;
    cin >> t;
    while (t --) {
        solve ();
    }
}

B. Mark the Dust Sweeper

题意:
[i, j)
[a[i],a[j-1]]全大于0,变:a[i]->a[i]-1; a[j]->a[j]+1
除a[n]外,全变为0
左减右加
分析:
x变为1的代价为x+1
//实质是打通0,堆积到尾部
统计中间的0的个数 + 每个数的代价之和

#include <bits/stdc++.h>
#define int long long

using namespace std;
const int N = 2e5 + 5;
int a[N];

void solve () {
    int n, st = -1, ans = 0;
    cin >> n;
    for (int i = 0; i < n; i ++) {
        cin >> a[i];
        ans += a[i];
        if (st == -1 && a[i] != 0)
            st = i;
    }
    if (st == n-1 || st == -1) {
        cout << "0\n";
        return ;
    }
    int cnt = 0;
    for (int i = st; i < n - 1; i ++) {
        if (a[i] == 0)
            cnt ++;
    }
    ans -= a[n-1];
    cout << cnt + ans << endl;    
    
}

signed main () {
    int t;
    cin >> t;
    while (t --) {
        solve ();
    }
}
//[i, j)
//[a[i],a[j-1]]全大于0,变:a[i]->a[i]-1; a[j]->a[j]+1
//除a[n]外,全变为0
//左减右加

//盲猜
//...x 0...代价为x+1
//......x 0代价为x


//打通到尾部
//统计中间的0的个数

C. Mark and His Unfinished Essay

从最后一段一直往前模拟
递推字符在串中的位置
(想不到啊...)
可以拿样例模拟一下来理解

#include <bits/stdc++.h>
#define int long long

using namespace std;
const int N = 2e5 + 5;
int a[N], n;
char s[N];

// int find (int x, vector<int> v[]) {
//     for (int i = 1; i <= n; i ++)
//         for (auto j : v[i]) {
//             if (x == j)
//                 return i;
//         }
//     return -1;
// }

void solve () {
    int n, m, q;
    //string s;
    cin >> n >> m >> q >> s;
    //s = ' ' + s;
    vector <int> len(m+1), l(m+1), r(m+1);
    len[0] = n;
    for (int i = 0; i < m; i ++) {
        cin >> l[i] >> r[i];
        len[i+1] = len[i] + r[i] - l[i] + 1;
    }

    while (q --) {
        int x;
        cin >> x;
        x --;
        for (int i = m - 1; i >= 0;  i --) {
            if (x >= len[i])
                x += l[i] - 1 - len[i];
        }
        cout << s[x] << endl;
    }
    
}

signed main () {
    int t;
    cin >> t;
    while (t --) {
        solve ();
    }
}
//从最后一段一直往前模拟
//递推字符在串中的位置


//对于每一个字母记录数字
//在对应的字母后面往后加
//+n*i-前面空出来的

D. Mark and Lightbulbs

题意:
给定串s,t
对于s[i-1]!=s[i+1],可变s[i](0变1,1变0)
求把s变成t需要多少步
分析:
e.g. 01001 可变为 01101,01011,00101
模拟多个样例后可发现,改变的本质是:
增加或减少连续段1的长度,但总连续段数量不变
故连续"1"段数量相同的可以互相转化
最小步数为端点差值

#include <bits/stdc++.h>
#define int long long

using namespace std;
typedef pair<int, int> pii;
const int N = 2e5 + 5;

void solve () {
    int n;
    string s, t;
    cin >> n >> s >> t;
    if (s[0] != t[0] || s[n-1] != t[n-1]) {
        cout << "-1\n";
        return ;
    }

    vector <pii> a, b;
    for (int i = 0; i < n; i ++) {
        if (s[i] == '0')    continue;
        int j = i;
        while (j + 1 < n && s[j + 1] == '1')    j ++;
        a.push_back ({i, j});
        i = j;
    }
    for (int i = 0; i < n; i ++) {
        if (t[i] == '0')    continue;
        int j = i;
        while (j + 1 < n && t[j + 1] == '1')    j ++;
        b.push_back ({i, j});
        i = j;
    }

    int m = a.size ();
    if (m != b.size ()) {
        cout << "-1\n";
        return ;
    }

    int ans = 0;
    for (int i = 0; i < m; i ++) {
        ans += abs (a[i].first - b[i].first) + abs (a[i].second - b[i].second);
    }
    cout << ans << endl;
}

signed main () {
    int t;
    cin >> t;
    while (t --) {
        solve ();
    }
}
//对于s[i-1]!=s[i+1],可变s[i]
//改变此处后,与其相邻的可变性取反

//本质:增加或减少连续段1的长度,但总连续段数量不变
//最小步数,端点差值

E. Mark and Professor Koro

题意:
操作:把两个相同的x替换成x+1,求最后剩下的数
q次修改,每次输出上述数字
分析:
二进制加减法(以左边为最低位)
加:找到右边第一个等于0的,改为1,中间全变为0
减:找到右边第一个等于1的,改为0,中间全变为1
"找": 线段树二分

注意:
加减是从左往右开始找
总查询是从右往左(因为要找最高位)

线段树上二分

原理:
1.判断当前区间是否符合(一般为区间最右端点),否则返回r+1
2.若l=r,返回。
3.查询左区间。
4.若左区间不符合,查询右区间。

过程中可以顺便查询答案和修改标记。

#include <bits/stdc++.h>

using namespace std;
const int N = 2e5 + 105, M = 2e5 + 100; //M!!
int a[N], cnt[N], n, m;

struct Node {
    int l, r, sum, add;
}st[N<<2];

void pushup (int u) {
    st[u].sum = st[u<<1].sum + st[u<<1|1].sum;
}

void update (int u, int add) {
    st[u].sum += (st[u].r - st[u].l + 1) * add;
    st[u].add += add;
}

void pushdown (int u) {
    if (st[u].add) {
        update (u << 1, st[u].add);
        update (u << 1 | 1, st[u].add);
        st[u].add = 0;
    }
}

void build (int u, int l, int r) {
    if (l == r) {
        st[u] = {l, r, cnt[r]};
        return ;
    }
    st[u] = {l, r};
    int mid = l + r >> 1;
    build (u << 1, l, mid);
    build (u << 1 | 1, mid + 1, r);
    pushup (u);
}

void modify (int u, int l, int r, int add) {
    if (st[u].l >= l && st[u].r <= r) {
        update (u, add);
        return ;
    }
    pushdown (u);
    int mid = st[u].l + st[u].r >> 1;
    if (l <= mid)   modify (u << 1, l, r, add);
    if (r > mid)    modify (u << 1 | 1, l, r, add);
    pushup (u);
}

//二分查找:加
int query0 (int u, int l) {
    if (st[u].r < l)    return -1;
    if (st[u].sum == st[u].r - st[u].l + 1)  return -1; //全1
    if (st[u].l == st[u].r)     return st[u].r;
    pushdown (u);
    int t = query0 (u << 1, l);
    if (~t) return t;
    return query0 (u << 1 | 1, l);
}

//二分查找:减
int query1 (int u, int l) {
    if (st[u].r < l)    return -1;
    if (st[u].sum == 0)  return -1; //全0
    if (st[u].l == st[u].r)     return st[u].r;
    pushdown (u);
    int t = query1(u << 1, l);
    if (~t) return t;
    return query1 (u << 1 | 1, l);
}

int querymax (int u) {
    if (st[u].l == st[u].r)   return st[u].r;
    pushdown(u);
    //高位开始查找
    if (st[u << 1 | 1].sum) return querymax (u << 1 | 1);
    return querymax (u << 1);
}

int main () {
    cin >> n >> m;
    for (int i = 1; i <= n; i ++) {
        cin >> a[i];
        cnt[a[i]] ++;
    }
    //二进制拆分
    for (int i = 1; i < M; i ++) {
        cnt[i + 1] += cnt[i] / 2;
        cnt[i] %= 2;
    }
    build (1, 1, M);

    while (m --) {
        int k, l, pos;
        cin >> k >> l;

        pos = query1 (1, a[k]);
        modify (1, pos, pos, -1);
        if (pos != a[k])    modify (1, a[k], pos - 1, 1);

        a[k] = l;

        pos = query0 (1, a[k]);
        modify (1, pos, pos, 1);
        if (pos != a[k])    modify (1, a[k], pos - 1, -1);

        cout << querymax (1) << endl;
    }
}

//操作:把两个相同的x替换成x+1,求最后剩下的数
//q次修改,每次输出上述数字

//二进制加减法(以左边为最低位)
//加:找到右边第一个等于0的,改为1,中间全变为0
//减:找到右边第一个等于1的,改为0,中间全变为1
//"找": 线段树二分

//注意:
//加减是从左往右开始找
//总查询是从右往左(因为要找最高位)

线段树的debug真是 _

posted @ 2022-07-16 12:25  Sakana~  阅读(111)  评论(0编辑  收藏  举报