Codeforces Round #602 (Div. 2, based on Technocup 2020 Elimination Round 3)

A - Math Problem

题意:给n条线段[l,r],求再加一条可以退化成点的线段,与所有线段各至少有一个公共点。

题解:求出最右边的左端点和最左边的右端点,左端点>右端点,则说明你的这个线段要把这两个连起来,否则你的线段就是现在的[左端点,右端点]上的随便一个点。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
 
void test_case() {
    int n;
    scanf("%d", &n);
    int L = 1, R = 1e9;
    for(int i = 1; i <= n; ++i) {
        int l, r;
        scanf("%d%d", &l, &r);
        L = max(L, l);
        R = min(R, r);
    }
    int ans = 0;
    if(L > R)
        ans = L - R;
    printf("%d
", ans);
}
 
int main() {
#ifdef KisekiPurin
    freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
    int t = 1;
    scanf("%d", &t);
    for(int ti = 1; ti <= t; ++ti) {
        //printf("Case #%d: ", ti);
        test_case();
    }
}

B - Box

题意:给一个前缀最大值序列,求满足这个序列的任意一个排列。

题解:生成的是满足这个序列的字典序最大的一个排列。而实际上因为这个序列是递增的,怎么乱构造都可以。首先每次最大值变化的位置肯定放这个罪魁祸首,然后扫一遍之后可能会有一些位置没有放,这个时候lower_bound其最大值得到大于等于其最大值的任意一个值。然后--之后就是小于它的最大的一个值,把它放过去就可以了。或者每次就放begin迭代器,直到begin迭代器超过最大值就-1,这样是字典序最小。

注:事实上并没有用到set的性质,所以应该是可以使用链表(但是链表不能用lower_bound然后--,或者说维护起来巨恶心)、并查集(并查集实现的伪单向链表)来实现的。并查集实现的双向链表比真正的双向链表好多了,不容易翻车。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
 
int q[100005];
int p[100005];
set<int> tmp;
 
void test_case() {
    int n;
    scanf("%d", &n);
    tmp.clear();
    for(int i = 1; i <= n; ++i) {
        p[i] = -1;
        tmp.insert(i);
    }
    for(int i = 1; i <= n; ++i)
        scanf("%d", &q[i]);
    p[1] = q[1];
    tmp.erase(q[1]);
    for(int i = 2; i <= n; ++i) {
        if(q[i] > q[i - 1]) {
            p[i] = q[i];
            tmp.erase(q[i]);
        }
    }
    for(int i = 1; i <= n; ++i) {
        if(p[i] == -1) {
            auto t = tmp.lower_bound(q[i]);
            if(t == tmp.begin()) {
                puts("-1");
                return;
            }
            --t;
            p[i] = *t;
            tmp.erase(t);
        }
    }
    for(int i = 1; i <= n; ++i)
        printf("%d%c", p[i], " 
"[i == n]);
}
 
int main() {
#ifdef KisekiPurin
    freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
    int t = 1;
    scanf("%d", &t);
    for(int ti = 1; ti <= t; ++ti) {
        //printf("Case #%d: ", ti);
        test_case();
    }
}
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

int p[100005];
int q[100005];

struct PseudoPrevLinkedList {
    int n;
    bool vis[100005];
    int prev[100005];

    void Init(int _n) {
        n = _n;
        for(int i = 1; i <= n; ++i) {
            vis[i] = 0;
            prev[i] = i - 1;
        }
    }

    int FindPrev(int x) {
        int r = prev[x];
        while(vis[r])
            r = prev[r];
        int t;
        while(prev[x] != r) {
            t = prev[x];
            prev[x] = r;
            x = t;
        }
        return r;
    }

    void Remove(int x) {
        vis[x] = 1;
    }
} fl;

void test_case() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) {
        p[i] = -1;
        scanf("%d", &q[i]);
    }
    fl.Init(n);
    p[1] = q[1];
    fl.Remove(p[1]);
    for(int i = 2; i <= n; ++i) {
        if(q[i] > q[i - 1]) {
            p[i] = q[i];
            fl.Remove(p[i]);
        }
    }
    for(int i = 1; i <= n; ++i) {
        if(p[i] == -1) {
            int res = fl.FindPrev(q[i]);
            if(!res) {
                puts("-1");
                return;
            }
            p[i] = res;
            fl.Remove(p[i]);
        }
    }
    for(int i = 1; i <= n; ++i)
        printf("%d%c", p[i], " 
"[i == n]);
}

int main() {
#ifdef KisekiPurin
    freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
    int t = 1;
    scanf("%d", &t);
    for(int ti = 1; ti <= t; ++ti) {
        //printf("Case #%d: ", ti);
        test_case();
    }
}

C - Messy

题意:给一个"("")"括号相等的长度不超过2000的括号串,要求每次反转一个子区间,最后使得括号串合法,且经过零点恰好k次。

题解:贪心,每次贪心把前两个字符变成"("")",这样最后就有n/2次零点,然后把开头开始的一小段破坏掉就可以了(假如需要的话,不要画蛇添足),至多用n/2+1次。因为网络的原因没有交上去,问题不大,实力到了自然会收敛。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

int n, k;
string s;
vector<pair<int, int> > ans;

void test_case() {
    ans.clear();
    cin >> n >> k >> s;
    int curp = 1;
    while(s.length()) {
        if(s[0] == '(' && s[1] == ')') {
            s = s.substr(2, s.length());
            curp += 2;
        } else if(s[0] == ')' && s[1] == '(') {
            ans.push_back({curp + 0, curp + 1});
            s = s.substr(2, s.length());
            curp += 2;
        } else if(s[0] == ')') {
            int j = 2;
            while(s[j] == s[0])
                ++j;
            ans.push_back({curp + 0, curp + j});
            string s1 = s.substr(0, j + 1);
            string s2 = s.substr(j + 1, s.length());
            reverse(s1.begin(), s1.end());
            s = s1 + s2;
            s = s.substr(2, s.length());
            curp += 2;
        } else {
            int j = 2;
            while(s[j] == s[0])
                ++j;
            ans.push_back({curp + 1, curp + j});
            string s0 = s.substr(0, 1);
            string s1 = s.substr(1, j);
            string s2 = s.substr(j + 1, s.length());
            reverse(s1.begin(), s1.end());
            s = s0 + s1 + s2;
            s = s.substr(2, s.length());
            curp += 2;
        }
    }
    int tk = n / 2 - k;
    if(1 + 2 * tk >= 2)
        ans.push_back({2, 1 + 2 * tk});
    cout << ans.size() << endl;
    for(int i = 0; i < ans.size(); ++i)
        cout << ans[i].first << " " << ans[i].second << endl;
}

int main() {
#ifdef KisekiPurin
    freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
    int t = 1;
    scanf("%d", &t);
    for(int ti = 1; ti <= t; ++ti) {
        //printf("Case #%d: ", ti);
        test_case();
    }
}

注:要注意string的substr方法的使用,真的烦死人,substr(a,b)是从a位置开始截取最多b个字符形成的子串。其实假如用静态字符数组会不会方便一些?要是自己写一个String类就可以使用自己的全新方法了。

D1 - Optimal Subsequences (Easy Version)

范围小到100,随便搞,用可持久化Treap/主席树就很简单了,直接在线回答,可惜空间消耗巨大。

D2 - Optimal Subsequences (Hard Version)

题意:给一个n个元素(<=2e5)序列,定义一个长度为k的最优子序列,当其和最大,且满足和最大的条件下字典序最小。每次询问一个k,pos,询问第k个子序列的第pos位置是哪个数。

题解:显然,和最大就是贪心,然后字典序最小的意思就是同大的优先贪前面的,所以按照题目意思排序。离线所有询问然后排序,然后每次往平衡树里面插新的元素直到变成k长的序列,然后找出每个pos位置是谁。然后就觉得很逗了,这个不是直接线段树就完了?具体来说就是线段树上二分,线段树上保存siz,然后利用siz的信息来找当前树上的第pos大,这个操作应该是去主席树那里抄一个就好了。

注:使用可持久化TreapRE了,然后改了一下上限之后WA了,所以还是对可持久化Treap的板子不熟,对空间消耗没有一个合理认识(假如你开512MB会这样?)。然后发现离线回答之后每次回答最短的k,然后就可以破坏掉了,不需要可持久化。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
 
struct FHQTreap {
#define ls(p) ch[p][0]
#define rs(p) ch[p][1]
    static const int MAXN = 200000 + 5;
    int val1[MAXN], val2[MAXN], ch[MAXN][2], rnd[MAXN], siz[MAXN], tot, root;
 
    void Init() {
        tot = root = 0;
    }
 
    void PushUp(int p) {
        siz[p] = siz[ls(p)] + siz[rs(p)] + 1;
    }
 
    void SplitValue(int p, int v, int &x, int &y) {
        if(!p) {
            x = y = 0;
            return;
        }
        if(v < val1[p]) {
            y = p;
            SplitValue(ls(p), v, x, ls(p));
            PushUp(y);
        } else {
            x = p;
            SplitValue(rs(p), v, rs(p), y);
            PushUp(x);
        }
    }
 
    void SplitRank(int p, int rk, int &x, int &y) {
        if(!p) {
            x = y = 0;
            return;
        }
        if(rk <= siz[ls(p)]) {
            y = p;
            SplitRank(ls(p), rk, x, ls(p));
            PushUp(y);
        } else {
            x = p;
            SplitRank(rs(p), rk - siz[ls(p)] - 1, rs(p), y);
            PushUp(x);
        }
    }
 
    int Merge(int x, int y) {
        if(!x || !y)
            return x | y;
        if(rnd[x] < rnd[y]) {
            rs(x) = Merge(rs(x), y);
            PushUp(x);
            return x;
        } else {
            ls(y) = Merge(x, ls(y));
            PushUp(y);
            return y;
        }
    }
 
    int NewNode(int v, int v2) {
        ++tot;
        ch[tot][0] = ch[tot][1] = 0;
        val1[tot] = v, val2[tot] = v2, rnd[tot] = rand();
        siz[tot] = 1;
        return tot;
    }
 
    void Insert(int &root, int v, int v2) {
        int x = 0, y = 0;
        SplitValue(root, v, x, y);
        root = Merge(Merge(x, NewNode(v, v2)), y);
    }
 
    void Remove(int &root, int v) {
        int x = 0, y = 0, z = 0;
        SplitValue(root, v, x, z);
        SplitValue(x, v - 1, x, y);
        y = Merge(ls(y), rs(y));
        root = Merge(Merge(x, y), z);
    }
 
    int GetRank(int &root, int v) {
        int x = 0, y = 0;
        SplitValue(root, v - 1, x, y);
        int rk = siz[x] + 1;
        root = Merge(x, y);
        return rk;
    }
 
    int GetValue2(int &root, int rk) {
        int x = 0, y = 0, z = 0;
        SplitRank(root, rk, x, z);
        SplitRank(x, rk - 1, x, y);
        int v2 = val2[y];
        root = Merge(Merge(x, y), z);
        return v2;
    }
#undef ls(p)
#undef rs(p)
} ft;
 
struct Node {
    int val;
    int pos;
    Node(int v = 0, int p = 0) {
        val = v;
        pos = p;
    }
    bool operator<(const Node &node)const {
        if(val != node.val)
            return val > node.val;
        return pos < node.pos;
    }
} node[200005];
 
struct Query {
    int id, k, pos, ans;
} q[200005];
 
bool cmp1(const Query &q1, const Query &q2) {
    if(q1.k != q2.k)
        return q1.k < q2.k;
    return q1.pos < q2.pos;
}
 
bool cmp2(const Query &q1, const Query &q2) {
    return q1.id < q2.id;
}
 
void InputQuery(int n) {
    for(int i = 1; i <= n; ++i) {
        scanf("%d%d", &q[i].k, &q[i].pos);
        q[i].id = i;
    }
    sort(q + 1, q + 1 + n, cmp1);
}
 
void OutputQuery(int n) {
    sort(q + 1, q + 1 + n, cmp2);
//    for(int i = 1; i <= n; ++i)
//        printf("%d%c", q[i].ans, " 
"[i == n]);
    for(int i = 1; i <= n; ++i)
        printf("%d
", q[i].ans);
}
 
void test_case() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &node[i].val);
        node[i].pos = i;
    }
    sort(node + 1, node + 1 + n);
    int m;
    scanf("%d", &m);
    InputQuery(m);
    ft.Init();
    int j = 1;
    for(int i = 1; i <= m; ++i) {
        while(ft.siz[ft.root] < q[i].k) {
            ft.Insert(ft.root, node[j].pos, node[j].val);
            ++j;
        }
        q[i].ans = ft.GetValue2(ft.root, q[i].pos);
    }
    OutputQuery(m);
}
 
int main() {
#ifdef KisekiPurin
    freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
    int t = 1;
    //scanf("%d", &t);
    for(int ti = 1; ti <= t; ++ti) {
        //printf("Case #%d: ", ti);
        test_case();
    }
}

E - 暂缺

F1 - Wrong Answer on test 233 (Easy Version)

题意:一套选择题,有n道题,每道题k个选项,选对得1分,选错得0分。求有多少种答案,使得其循环右移一次之后得分比之前严格高,模998244353。

题解:看起来很像dp,但是怎么搞?

原文地址:https://www.cnblogs.com/KisekiPurin2019/p/11924913.html