Codeforces Round #602

Codeforces Round #602

这场比赛本来是抓着当暑假集训的个人赛的,但是打得还不错,就想写篇题解。

Math Problem

传送门

开一个mx记录左端点的最大值,开一个mn记录右端点的最小值,答案是max(mx - mn, 0)。

#include <iostream>
using namespace std;
#define INF 0x7fffffff
int main()
{
    int _;
    cin >> _;
    while(_--)
    {
        int n;
        cin >> n;
        int mx = -INF, mn = INF;
        for(int i = 1; i <= n; i++)
        {
            int l, r;
            cin >> l >> r;
            mx = max(mx, l);
            mn = min(mn, r);
        }
        cout << max(mx - mn, 0) << endl;
    }
    return 0;
}

Box

传送门

题目中给出的q数组中每个数第一次出现的位置就是那个数在p数组中出现的位置,然后将没有出现过的数按照由小到大的顺序插入到p数组中空缺的位置,之后再对p数组跑一遍前缀最大值,如果跑出来的结果跟题目中给出的q数组有矛盾的话输出-1,否则输出构造好的p数组。

#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
#define N 100005
int q[N], p[N], t[N], flag[N];
vector <int> v;
int main()
{
    int _;
    cin >> _;
    while(_--)
    {
        v.clear();
        memset(q, 0, sizeof(q));
        memset(p, 0, sizeof(p));
        memset(t, 0, sizeof(t));
        memset(flag, 0, sizeof(flag));
        int n;
        cin >> n;
        for(int i = 1; i <= n; i++)
        {
            cin >> q[i];
            if(!flag[q[i]])
            {
                flag[q[i]] = 1;
                p[i] = q[i];
            }
        }
        for(int i = 1; i <= n; i++)
            if(!flag[i])
                v.push_back(i);
        int cnt = 0;
        for(int i = 1; i <= n; i++)
            if(!p[i])
                p[i] = v[cnt++];
        for(int i = 1; i <= n; i++)
            t[i] = max(t[i - 1], p[i]);
        int f = 1;
        for(int i = 1; i <= n; i++)
        {
            if(q[i] != t[i])
            {
                f = 0;
                break;
            }
        }
        if(f)
        {
            for(int i = 1; i <= n; i++)
            {
                if(i == 1)
                    cout << p[i];
                else
                    cout << " " << p[i];
            }
            cout << endl;
        }
        else
            cout << -1 << endl;
    }
    return 0;
}

Messy

传动门

考虑先将所有的左括号移到左边,右括号移到右边,可以这样操作:遍历1 ~ n,如果当前字符是右括号,就一直向右找到它右边的第一个左括号,然后翻转这段区间,这样最多经过n / 2步就能达到我们的目的。之后的任务就是根据题目的要求构造出k个前缀,怎么构造呢?会发现在所有的左括号都在左边,右括号都在右边的情况下,只需要将2 * i到n / 2 + i这段区间翻转就能多出来一个前缀,所以只需要这样翻转就能达到题目的要求。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
#define N 2005
char s[N];
vector <pair <int, int> > ans;
int main()
{
    int _;
    cin >> _;
    while(_--)
    {
        ans.clear();
        int n, k;
        cin >> n >> k;
        cin >> s + 1;
        for(int i = 1; i <= n; i++)
        {
            if(s[i] == ')')
            {
                int st = i, ed = i + 1;
                while(ed <= n && s[ed] == ')')
                    ed++;
                if(ed == n + 1)
                    break;
                ans.push_back({st, ed});
                reverse(s + st, s + ed + 1);
            }
        }
        for(int i = 1; i < k; i++)
        {
            ans.push_back({2 * i, n / 2 + i});
            reverse(s + 2 * i, s + n / 2 + i + 1);
        }
        cout << ans.size() << endl;
        for(int i = 0; i < ans.size(); i++)
            cout << ans[i].first << " " << ans[i].second << endl;
    }
    return 0;
}

** Optimal Subsequences (Easy Version)**

传送门

下一道题的简单版本,数据很水(应该叫小),直接暴力就能过。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 105
int a[N], vis[N];
struct Node
{
    int a;
    int pos;
}node[N];
bool cmp(Node x, Node y)
{
    if(x.a != y.a)
        return x.a > y.a;
    else
        return x.pos < y.pos;
}
int main()
{
    int n;
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        cin >> a[i];
        node[i].a = a[i];
        node[i].pos = i;
    }
    sort(node + 1, node + n + 1, cmp);
    int m;
    cin >> m;
    while(m--)
    {
        memset(vis, 0, sizeof(vis));
        int k, pos;
        cin >> k >> pos;
        for(int i = 1; i <= k; i++)
            vis[node[i].pos] = 1;
        int cnt = 0;
        for(int i = 1; i <= n; i++)
        {
            if(vis[i])
                cnt++;
            if(cnt == pos)
            {
                cout << a[i] << endl;
                break;
            }
        }
    }
    return 0;
}

Optimal Subsequences (Hard Version)

数据放大到2e5之后就不能再暴力了,来想正常的解法,顺着上一题的思路,根据题目的要求,我们先选的数是较大的,在数一样大的情况下选的是出现较靠前的,每次都问我们选出来的序列中下标是pos的数是多少,可以这样考虑:建立一颗线段树,线段树维护原数组某个区间内被选出的数的个数,那么当我们选完数之后,问我们选出来的序列中下标是pos的数是多少不就可以转换成线段树求区间第k小了吗?这个问题解决了,但是可能会有很多次在线段树上插入点删除点的操作,m次询问,最多有mn次插入点删除点的操作,复杂度mnlogn,直接暴上天,怎么解决这个问题?很简单,离线处理。将所有的询问都记录下来,如果按照k由小到大排序,那么就只用写ins函数就行;如果按照k由大到小排序,那么就只用写del函数就行。在这里我是按照k由小到大排序写的,之后就是在线段树上插点查询了,复杂度O(nlogn + mlogm + mlogn)。

#include <iostream>
#include <algorithm>
using namespace std;
#define N 200005
int a[N], ans[N];
struct Number
{
    int a;
    int pos;
}number[N];
struct Q
{
    int k;
    int pos;
    int id;
}q[N];
struct Node
{
    int l;
    int r;
    int val;
    int sum;
}node[N << 2];
bool cmp1(Number x, Number y)
{
    if(x.a != y.a)
        return x.a > y.a;
    else
        return x.pos < y.pos;
}
bool cmp2(Q x, Q y)
{
    if(x.k != y.k)
        return x.k < y.k;
    else if(x.pos != y.pos)
        return x.pos < y.pos;
    else
        return x.id < y.id;
}
void push_up(int rt)
{
    node[rt].sum = node[rt << 1].sum + node[rt << 1 | 1].sum;
}
void build(int rt, int l, int r)
{
    if(l == r)
        node[rt] = {l, r, a[l], 0};
    else
    {
        node[rt] = {l, r, 0, 0};
        int m = l + r >> 1;
        build(rt << 1, l, m);
        build(rt << 1 | 1, m + 1, r);
    }
}
void ins(int rt, int pos)
{
    if(node[rt].l == pos && node[rt].r == pos)
        node[rt].sum = 1;
    else
    {
        int m = node[rt].l + node[rt].r >> 1;
        if(pos <= m)
            ins(rt << 1, pos);
        else
            ins(rt << 1 | 1, pos);
        push_up(rt);
    }
}
int query(int rt, int pos)
{
    if(node[rt].l == node[rt].r)
        return node[rt].val;
    else
    {
        if(node[rt << 1].sum >= pos)
            return query(rt << 1, pos);
        else
            return query(rt << 1 | 1, pos - node[rt << 1].sum);
    }
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        cin >> a[i];
        number[i].a = a[i];
        number[i].pos = i;
    }
    sort(number + 1, number + n + 1, cmp1);
    int m;
    cin >> m;
    for(int i = 1; i <= m; i++)
    {
        cin >> q[i].k >> q[i].pos;
        q[i].id = i;
    }
    sort(q + 1, q + m + 1, cmp2);
    build(1, 1, n);
    int cnt = 0;
    for(int i = 1; i <= m; i++)
    {
        while(cnt < q[i].k)
            ins(1, number[++cnt].pos);
        ans[q[i].id] = query(1, q[i].pos);
    }
    for(int i = 1; i <= m; i++)
        cout << ans[i] << endl;
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值