• [Codeforces]Educational Codeforces Round 85 赛后总结


    Before the Beginning

    转载请将本段放在文章开头显眼处,如有二次创作请标明。
    原文链接:https://www.codein.icu/cfedu85/

    前言

    好久没有打比赛了,周五考完文化课之后看到有深夜场,想了想就打了。
    最后只做出来4题,排名一千左右。
    在此写一个总结,如有必要也会发布单题题解。咕咕咕

    A. Level Statistics

    题目传送门

    签到题,有点小坑。

    题意

    一个人在玩游戏,给出若干个状态,以 ((p_i,c_i)) 来表示他现在进行了 (p_i) 次游戏,通关了 (c_i) 次。

    解法

    很显然,对于每个状态,有如下限制:

    1. (c_i leq p_i),因为通关次数不可能比游戏次数还多。
    2. (c_i - c_{i-1} leq p_i - p_{i-1}),相对于上次的状态,增加的通关次数不可能比增加的游戏次数多。
    3. (c_{i-1} leq c_i,p_{i-1} leq p_i),时间顺序,无需多说。

    有一个坑点就是状态是按时间顺序给出的。只有愚蠢的笔者才会看不懂描述跳这个坑吧
    所以并不需要按通关次数等等进行排序。

    Code

    给出比赛时代码。

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    template<typename T>
    void read(T &r)
    {
        static char c;r=0;
        for(c=getchar();c>'9'||c<'0';c=getchar());
        for(;c>='0'&&c<='9';r=(r<<1)+(r<<3)+(c^48),c=getchar());
    }
    const int maxn = 110;
    int T;
    int n;
    struct node
    {
        int p,c;
    }a[maxn];
    bool cmp(const node &a,const node &b)
    {
        if(a.c == b.c)
            return a.p < b.p;
        return a.c < b.c;
    }
    int main()
    {
        read(T);
        while(T--)
        {
            read(n);
            for(int i = 1;i<=n;++i)
                read(a[i].p),read(a[i].c);
            for(int i = 1;i<=n;++i)
            {
                if(a[i].c > a[i].p)
                {
                    puts("NO");
                    goto end;
                }
                if(a[i].c < a[i-1].c)
                {
                    puts("NO");
                    goto end;
                }
                if(a[i].p < a[i-1].p)
                {
                    puts("NO");
                    goto end;
                }
                if(a[i].c - a[i-1].c > a[i].p - a[i-1].p)
                {
                    puts("NO");
                    goto end;
                }
            }
            puts("YES");
            end:;
        }
        return 0;
    }
    

    B. Middle Class

    题目传送门

    水题,没啥难度。

    题意

    给出一堆人,可以从中选出一部分人,将其总财富均分。
    定义财富多于 (x) 的为中产阶级。
    问最多有多少中产阶级。

    解法

    显然要劫富济贫,而总财富需求与人数线性相关。
    于是按财富排降序。
    计算出假定前 (i) 个人都是中产所需的总财富。
    计算出当前总财富。
    两者相比较,如果当前总财富可以满足则继续向下枚举。

    Code

    给出比赛时代码。

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    template<typename T>
    void read(T &r)
    {
        static char c;r=0;
        for(c=getchar();c>'9'||c<'0';c=getchar());
        for(;c>='0'&&c<='9';r=(r<<1)+(r<<3)+(c^48),c=getchar());
    }
    const int maxn = 1e5 + 10;
    int T,n,x;
    int a[maxn];
    bool cmp(int a,int b)
    {
        return a>b;
    }
    int main()
    {
        read(T);
        while(T--)
        {
            read(n);
            read(x);
            for(int i = 1;i<=n;++i)
                read(a[i]);
            sort(a+1,a+1+n);
            long long now = 0;
            long long need = 0;
            int ans = 0;
            for(int i = n;i;--i)
            {
                now += a[i];
                need += x;
                if(now < need)
                {
                    printf("%d
    ",ans);
                    goto end;
                }
                ++ans;
            }
            if(ans == n)
                printf("%d
    ",ans);
            else if(ans == 0)
                puts("0");
            end:;
        }
        return 0;
    }
    

    CF1334C Circle of Monsters

    题目传送门

    思考一会可以得到结论的贪心题。

    题意

    给出一圈怪兽,每个怪兽有两个属性 (a_i,b_i),分别代表生命值和死亡后对下一个怪兽的伤害值。
    要求最少造成多少伤害就可以杀光怪兽。

    解法

    首先有一个显然的结论:按顺序杀一定最优。

    因为每个怪兽所受到伤害只有两个来源:

    1. 直接造成伤害
    2. 前一个怪兽死亡造成伤害

    而开始造成伤害后,因为这只怪兽一定要死,那么肯定是先杀前面一个能更省力。

    按顺序杀光怪兽的话,枚举开始点,直接模拟显然是一种可行的做法。
    但模拟复杂度 (O(n^2)) 无法通过,可以用预处理降复杂度。

    (c_i) 为前一只怪兽死后,这只怪兽还剩余的血量。
    (c_i = a_i - b_{i-1}),边界特殊处理一下即可。

    那么求出 (c) 的总和,记为 (csum)
    假定现在从第 (i) 只怪兽开始杀,那么:
    (csum) 中减去 (c_i),再加上 (a_i),即为当前情况的总花费伤害。

    复杂度降低到 (O(n)),可以通过。

    Code

    给出比赛时的代码。

    #include <cstdio>
    using namespace std;
    template<typename T>
    inline T _max(const T &a,const T &b){return a>b?a:b;}
    template<typename T>
    void read(T &r)
    {
        static char c;r=0;
        for(c=getchar();c>'9'||c<'0';c=getchar());
        for(;c>='0'&&c<='9';r=(r<<1)+(r<<3)+(c^48),c=getchar());
    }
    const int maxn = 3e5 + 10;
    int T,n;
    long long a[maxn],b[maxn],c[maxn];
    int main()
    {
        read(T);
        while(T--)
        {
            read(n);
            for(int i = 1;i<=n;++i)
                read(a[i]),read(b[i]);
            long long sum = 0;
            for(int i = 2;i<=n;++i)
                sum += (c[i] = _max(a[i] - b[i-1],0ll));
            sum += (c[1] = _max(a[1] - b[n],0ll));
            int minn = 1;
            long long ans = 1ll << 60;
            for(int i = 1;i<=n;++i)
            {
                long long ret = sum - c[i] + a[i];
                if(ret < ans)
                    ans = ret;
            }
            printf("%lld
    ",ans);
        }
        return 0;
    }
    

    D. Minimum Euler Cycle

    题目传送门

    依然是贪心的题目。但笔者将其做成了找规律……

    题意

    给出一张包含 (n) 个顶点的完全图。
    要求出一条路径,使得每条边都经过恰好一次。(单向边)
    并且要求经过顶点字典序最小。

    解法

    笔者先从贪心入手。
    很显然从顶点 (1) 出发,去往顶点 (2)
    此时可以回到顶点 (1) 使得字典序最小,也可以选择去往其他顶点。
    此时笔者考虑到回到顶点 (1) 后可能无法完成图的遍历,因此暂时搁置了这个想法。

    由于数据范围大,笔者猜想一定有规律可以快速求解。
    所以笔者写了个 (O(n^2n!)) 的暴力来打表。

    #include <cstdio>
    #include <cstring>
    using namespace std;
    const int maxn = 50;
    int n;
    int vis[maxn][maxn];
    int path[maxn];
    int temp[maxn];
    int maxt;
    void dfs(int u,int t)
    {
        temp[t] = u;
        maxt = t>maxt?t:maxt;
        bool over = true;
        for(int i = 1;i<=n;++i)
            for(int j = 1;j<=n;++j)
                if(i != j && !vis[i][j])
                    over = false;
        if(over)
        {
            for(int i = 1;i<=maxt;++i)
                if(temp[i] < path[i])
                {
                    for(int j = 1;j<=maxt;++j)
                        path[j] = temp[j];
                    break;
                }
                else if(temp[i] > path[i])
                    break;
            return;
        }
        for(int i = 1;i<=n;++i)
            if(i != u && !vis[u][i])
            {
                vis[u][i] = 1;
                dfs(i,t+1);
                vis[u][i] = 0;
            }
    }
    int main()
    {
        memset(path,0x3f,sizeof(path));
        scanf("%d",&n);
        dfs(1,1);
        for(int i = 1;i<=maxt;++i)
            printf("%d ",path[i]);
        return 0;
    }
    

    打表后发现,字典序最小的路径如下形式:

    n = 2: 
    1 2 
    1
    
    n = 3: 
    1 2 1 3
    2 3
    1
    
    n = 4:
    1 2 1 3 1 4
    2 3 2 4
    3 4
    1
    ......
    

    找到规律了,此时可以结合之前的贪心思路:
    由顶点 (1) 访问到其他点后,再回到顶点 (1) 可以使得字典序最小。
    依次访问到最后一点后,可以去往顶点 (2) 使得字典序最小。
    然后如法炮制,不断访问……
    最后从最后的顶点再回到顶点 (1)
    刚好全部遍历一遍。

    Code

    代码实现上,笔者运用了前文的像金字塔一样的规律。
    预处理出每层的节点数量,以判断所求左右端点在哪一层。
    随后依次输出即可。
    层内规律:
    设节点在第 (k) 层的第 (i) 个节点,那么 (i) 为奇数时,输出 (k),否则输出 (k + frac{i}{2})
    最后一点是 (1),特判一下即可。

    #include <cstdio>
    using namespace std;
    template<typename T>
    void read(T &r)
    {
        static char c;r=0;
        for(c=getchar();c>'9'||c<'0';c=getchar());
        for(;c>='0'&&c<='9';r=(r<<1)+(r<<3)+(c^48),c=getchar());
    }
    const int maxn = 1e5 + 10;
    int T;
    long long n,l,r;
    long long s[maxn];
    long long f;
    int main()
    {
        read(T);
        while(T--)
        {
            bool flag = false;
            long long lp = 1,rp = 1;
            long long lp2;
            long long rp2;
            read(n);
            f = n * (n-1) + 1;
            read(l);
            read(r);
            for(int i = 1;i<=n;++i)
                s[i] = ((n-i) << 1) + s[i-1];
            if(l == s[n]+1)
            {
                flag = true;
                goto end;
            }
            if(r == s[n]+1)
            {
                flag = true;
                --r;
            }
            while(s[lp] < l)
                ++lp;
            while(s[rp] < r)
                ++rp;
            lp2 = l - s[lp-1];
            rp2 = r - s[rp-1];
            if(lp == rp)
            {
                for(int i = lp2;i<=rp2;++i)
                {
                    if(i & 1)
                        printf("%lld ",lp);
                    else
                        printf("%lld ",lp + (i>>1));
                }
                goto end;
            }
            for(long long i = lp2;i<=s[lp] - s[lp-1];++i)
            {
                if(i & 1)
                    printf("%lld ",lp);
                else
                    printf("%lld ",lp + (i>>1));
            }
            for(long long i = lp+1;i<rp;++i)
            {
                for(long long p = 1;p<=s[i] - s[i-1];++p)
                {
                    if(p & 1)
                        printf("%lld ",i);
                    else
                        printf("%lld ",i + (p>>1));
                }
            }
            for(long long i = 1;i<=rp2;++i)
            {
                if(i & 1)
                    printf("%ld ",rp);
                else
                    printf("%lld ",rp + (i >> 1));
            }
            end:
            if(flag)
                printf("1");
            printf("
    ");
        }
        return 0;
    }
    

    End

    打完 D 后只剩下半小时,笔者就放弃去睡觉了。
    后续题目笔者可能会补题,有必要的会单独发题解。
    咕咕咕

  • 相关阅读:
    go 算法之插入排序算法 running
    Go多协程并发环境下的错误处理 running
    go 协程控制之sync.Once{} running
    php 生成指定范围内不重复的随机数 running
    php 正则函数preg_replace_callback running
    go 算法之冒泡算法 running
    googleStyle
    ubuntu搭建APT源简单方法
    request.getParameterMap()快速转化为bean
    多线程编程CountDownLatch和CyclicBarrier
  • 原文地址:https://www.cnblogs.com/Clouder-Blog/p/cfedu85.html
Copyright © 2020-2023  润新知