码迷,mamicode.com
首页 > 其他好文 > 详细

Educational Codeforces Round 76 (Rated for Div. 2)

时间:2019-11-14 09:43:32      阅读:83      评论:0      收藏:0      [点我收藏+]

标签:name   print   vat   为什么   put   sort   问题   比较   复杂度   

A - Two Rival Students

题意:共n个人排一排,两个人,位于a,b,相邻位置交换至多x次,最大化abs(a-b)的值。

题解:每次交换至多+1,不能超过n-1。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
 
int main() {
#ifdef KisekiPurin
    freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
    int t;
    scanf("%d", &t);
    while(t--) {
        int n, x, a, b;
        scanf("%d%d%d%d", &n, &x, &a, &b);
        printf("%d\n", min(n - 1, abs(a - b) + x));
    }
    return 0;
}

B - Magic Stick

题意:给一个数a,有无限次的两种操作:1、若a是偶数,则加上a的一半。2、若a>1,则减去1。问是否可以从x构造出y。

题解:有无限次减法,就看1操作能到达哪里就可以。首先1是不能动的,y<=1。其次,2可以到3,3可以到2,没办法突破到4,所以x=2或x=3时,y<=3。而4可以有4->6->9->8->12->18->27->26->39->...这样无限做下去,所以4以上必有解。

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

int main() {
#ifdef KisekiPurin
    freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
    int t;
    scanf("%d", &t);
    while(t--) {
        ll x, y;
        scanf("%lld%lld", &x, &y);
        bool suc = 0;
        if(x == 1)
            suc = (y <= 1);
        else if(x <= 3)
            suc = (y <= 3);
        else
            suc = 1;
        puts(suc ? "YES" : "NO");
    }
    return 0;
}

C - Dominated Subarray

题意:n(n<=2e5)个数的序列a,ai<=n,找最短的“被支配子区间”,一个子区间被支配有两个条件:1、长度>=2,2、有唯一的众数。

题解:一开始还想二分,二分长度然后验证,还写了一个验证的算法,可以均摊O(1)转移出众数(每次尺取的时候,众数至多变化1)。但是转头一想,小的区间不行大的未必不行的。比如我构造一个这样的:

1
7
2 2 4 4 2 2 4

假如一开始二分到4,则验证失败,任意一个子区间的众数都是2和4两个。但最短子区间长度是2。

后来想了一下,是不是最开始考虑的,是否是最短的两个相邻相同元素的差呢?简单证明之后发现的确是这样。

假如最短的两个相邻相同元素为下面的样子:

x,y1,y2,y3,...,yk,x

那么yi互不相等,也当然和x不相等,否则有更短的yi,yj与假设矛盾。则这个区间一定是“被支配子区间”。

但为什么最短“被支配子区间”一定是这样呢?

从x往两边扩展,长度肯定不会更短,而往内收缩,则不会有更短的“被支配子区间”,所以这样的相邻x组成的为被支配子区间的构成元素,长的可以由若干个小的组合得到。在这个算法下n=1不需要特判。注意别被卡memset。

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

int n, a[200005];
int pre[200005];

int main() {
#ifdef KisekiPurin
    freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
    int t;
    scanf("%d", &t);
    while(t--) {
        scanf("%d", &n);
        memset(pre, -1, sizeof(pre[0]) * (n + 1));
        for(int i = 1; i <= n; ++i)
            scanf("%d", &a[i]);

        int minlen = 1e9;
        for(int i = 1; i <= n; ++i) {
            if(pre[a[i]] != -1)
                minlen = min(minlen, i - pre[a[i]] + 1);
            pre[a[i]] = i;
        }
        if(minlen == 1e9)
            minlen = -1;

        printf("%d\n", solve());
    }
    return 0;
}

但是貌似t只有1000,在cf是卡不了memset的样子。

二分的错误算法,不过算是知道怎么O(1)转移众数了。

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

int n, a[200005];
int occ[200005];
int maxi[200005];

bool check(int len) {
    memset(occ, 0, sizeof(occ[0]) * (n + 1));
    memset(maxi, 0, sizeof(maxi[0]) * (n + 1));
    int maxtop = 0;
    for(int i = 1; i <= len; ++i) {
        --maxi[occ[a[i]]];
        ++occ[a[i]];
        ++maxi[occ[a[i]]];
        maxtop = max(maxtop, occ[a[i]]);
    }
    if(maxi[maxtop] == 1)
        return 1;
    else {
        for(int i = len + 1; i <= n; ++i) {
            --maxi[occ[a[i]]];
            ++occ[a[i]];
            ++maxi[occ[a[i]]];

            --maxi[occ[a[i - len]]];
            --occ[a[i - len]];
            ++maxi[occ[a[i - len]]];
            while(maxi[maxtop] == 0)
                --maxtop;
            maxtop = max(maxtop, occ[a[i]]);
            if(maxi[maxtop] == 1)
                return 1;
        }
    }
    return 0;
}

int solve() {
    int l = 2, r = n;
    //printf("l=%d r=%d\n", l, r);
    while(1) {
        int m = l + r >> 1;
        printf("m=%d\n", m);
        if(l == m) {
            if(check(l))
                return l;
            else if(check(r))
                return r;
            else
                return -1;
        }
        if(check(m))
            r = m;
        else
            l = m + 1;
    }
}

int main() {
#ifdef KisekiPurin
    freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
    int t;
    scanf("%d", &t);
    while(t--) {
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i)
            scanf("%d", &a[i]);
        if(n == 1) {
            puts("-1");
            continue;
        }
        printf("%d\n", solve());
    }
    return 0;
}

D - Yet Another Monster Killing Problem

题意:有n个怪物,从左到右刷,每个怪攻击力ai,你有m个英雄,每个英雄有攻击力pi和耐力si。每天你放一个英雄往前刷,直到耐力消耗完/怪刷完/遇到攻击比他高的怪,他就会回城。英雄可以按任意顺序用任意次,无解输出-1。

题解:一开始想的是,肯定是个偏序关系,有用的英雄要么攻击高,要么耐力高,组成一条折线(一开始还在想凸壳),然后每一天就贪心尽可能往前走,由于英雄被筛选过,所以往前走d步要找的是si>=d的第一个英雄。找不到就-1。找得到就比较两者攻击力,攻击力不够也是-1,这样貌似变成一个RMQ问题。

所以排序筛选之后贪心,二分往前走的值,然后用ST表RMQ,是严格的O(nlogn)。

注意ST表的访问不能够越界!

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

struct Hero {
    int s, p;
    bool operator<(const Hero &h)const {
        return s != h.s ? s < h.s : p < h.p;
    }
} h[200005], H[200005];
int n, hn, a[200005];
int st[200005], stop;

int id[200005];
//耐力超过i的攻击力最高的英雄的编号为id[i];
int cur;

class ST_Table {
  private:
    static const int MAXLOGN = 19;
    static const int MAXN = 200005;
    int logn[MAXN + 5];
    int f[MAXN + 5][MAXLOGN + 1];

  public:
    inline void init1() {
        logn[1] = 0;
        for(int i = 2; i <= MAXN; i++) {
            logn[i] = logn[i / 2] + 1;
        }
    }

    inline void init2(int n) {
        for(int i = 1; i <= n; i++)
            f[i][0] = a[i];
        for(int j = 1; j <= MAXLOGN; j++)
            for(int i = 1; i + (1 << j) - 1 <= n; i++)
                f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
    }

    inline int range_max(int l, int r) {
        int s = logn[r - l + 1];
        return max(f[l][s], f[r - (1 << s) + 1][s]);
    }
} ST;

bool check(int len) {
    int p = id[len];
    if(p == -1)
        return 0;
    else
        return H[p].p >= ST.range_max(cur + 1, min(n, cur + len));
}

int solve() {
    int l = 1, r = H[hn].s;
    while(1) {
        int m = l + r >> 1;
        if(l == m) {
            if(check(r))
                return r;
            else if(check(l))
                return l;
            else
                return -1;
        }
        if(check(m))
            l = m;
        else
            r = m - 1;
    }
}

int main() {
#ifdef KisekiPurin
    freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
    ST.init1();
    int t;
    scanf("%d", &t);
    while(t--) {
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i)
            scanf("%d", &a[i]);

        ST.init2(n);
        scanf("%d", &hn);
        for(int i = 1; i <= hn; ++i) {
            scanf("%d%d", &h[i].p, &h[i].s);
        }
        sort(h + 1, h + 1 + hn);
        stop = 0;
        for(int i = 1; i <= hn; ++i) {
            while(stop && h[i].p >= h[st[stop]].p)
                --stop;
            st[++stop] = i;
        }

        hn = stop;
        for(int i = 1; i <= hn; ++i)
            H[i] = h[st[i]];

        for(int i = 1, curid = 1; i <= n; ++i) {
            while(curid <= hn && H[curid].s < i)
                ++curid;
            if(H[curid].s >= i)
                id[i] = curid;
            else
                id[i] = -1;
        }

        cur = 0;
        int ans = 0;
        while(cur < n) {
            int d = solve();
            if(d == -1) {
                ans = -1;
                break;
            } else {
                cur += d;
                ++ans;
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

注:要筛选出一个有用的偏序集合的时候,貌似是排序然后单调栈是最好理解的。在这题中,因为维度s的取值范围足够小,可以不进行排序而使用dp来进行转移,具体做法就是在每个耐力值上打上最大的pi标记,然后从n向1传递pi,这样形成的就是一个偏序集合,复杂度O(n+m)。

Educational Codeforces Round 76 (Rated for Div. 2)

标签:name   print   vat   为什么   put   sort   问题   比较   复杂度   

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

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!