• Codeforces Round #615 (Div. 3)


    暂时没有F题的题解。太菜了。

    A - Collecting Coins

    题意:有三个人,分别有a,b,c枚硬币,你有n枚硬币,要求把n枚硬币全部给这三个人并且使得他们的硬币数变为相等。问是否可行。

    题解:先验证硬币总数a+b+c+n是否能被3整除,然后验证要补的硬币的数量少于n。

    void test_case() {
        int a[5], n;
        scanf("%d%d%d%d", &a[1], &a[2], &a[3], &n);
        sort(a + 1, a + 1 + 3);
        int sum = a[3] - a[1] + a[3] - a[2];
        if(sum > n || (sum - n) % 3 != 0)
            puts("NO");
        else
            puts("YES");
    }
    

    B - Collecting Packages

    题意:有个机器人,只能向上走或者向右走,要从(0,0)经过所有的点(xi,yi),求是否可行,若可行则输出字典序最小的方案。

    题解:字典序最小就是说先往右走再往上走。直接把所有点按x排序,要求每个点与(0,0)包围的矩形包含此前的所有点,只需要比前面的点都高就行了。由数学归纳法可知只需要比前一个点高就行了。

    int n;
    pii p[1005];
     
    void test_case() {
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i) {
            int x, y;
            scanf("%d%d", &x, &y);
            p[i] = {x, y};
        }
        sort(p + 1, p + 1 + n);
        for(int i = 2; i <= n; ++i) {
            if(p[i].second < p[i - 1].second) {
                puts("NO");
                return;
            }
        }
        puts("YES");
        int cx = 0, cy = 0;
        for(int i = 1; i <= n; ++i) {
            while(cx < p[i].first) {
                putchar('R');
                ++cx;
            }
            while(cy < p[i].second) {
                putchar('U');
                ++cy;
            }
        }
        putchar('
    ');
    }
    

    C - Product of Three Numbers

    题意:给一个数n,满足n>=2,要求将n分解成互异的三个数a,b,c,使得a*b*c==n,且a,b,c>=2。给出一种分解的方案,或者说明其不存在。

    题解:一开始没看见>=2直接判断质数就上了(虽然这样也不对,比如9不能分解为1,3,3),仔细一想跟质数的种类有关系,所以可以直接分解质因数。假如有3种以上的质因数,那么前两个数要两种,剩下的最后一个数补位就是一种字典序最小的构造。假如只有1种质因数,那么肯定至少需要6次,前两个数分别要1次和2次,剩下的至少要3次,这也是一种字典序最小的构造。假如有2种质因数,那么总次数<=3的显然是无解的,而总次数>=4的必定有解。首先把两种质因数各1次拿出来,那么剩下的那个2次也不会和前面的重复,只是这样并不是字典序最小(还好题目没要求)。

    最简单的思路也是字典序最小的思路就是直接暴力枚举a和b。

    下面这个质因数分解的模板貌似还带有筛出最小质因子的功能的。

    const int MAXN = 1e5;
    int p[MAXN + 5], ptop;
    int pn[MAXN + 5];
     
    void sieve() {
        int n = MAXN;
        pn[1] = 1;
        for(int i = 2; i <= n; i++) {
            if(!pn[i])
                p[++ptop] = i;
            for(int j = 1; j <= ptop; j++) {
                int t = i * p[j];
                if(t > n)
                    break;
                pn[t] = p[j];
                if(i % p[j] == 0)
                    break;
            }
        }
    }
     
    int fac[105][2], ftop;
     
    void get_fac(int n) {
        ftop = 0;
        for(int i = 1; i <= ptop; ++i) {
            if(n % p[i] == 0) {
                fac[++ftop][0] = p[i];
                fac[ftop][1] = 0;
                while(n % p[i] == 0) {
                    n /= p[i];
                    ++fac[ftop][1];
                }
            }
        }
        if(n > 1) {
            fac[++ftop][0] = n;
            fac[ftop][1] = 1;
        }
    }
     
    void test_case() {
        int n;
        scanf("%d", &n);
        get_fac(n);
        if(ftop >= 3) {
            puts("YES");
            printf("%d %d %d
    ", fac[1][0], fac[2][0], n / fac[1][0] / fac[2][0]);
            return;
        } else if(ftop == 1) {
            if(fac[1][1] >= 6) {
                puts("YES");
                printf("%d %d %d
    ", fac[1][0], fac[1][0]*fac[1][0], n / fac[1][0] / fac[1][0] / fac[1][0]);
                return;
            } else {
                puts("NO");
                return;
            }
        } else {
            int sum = fac[1][1] + fac[2][1];
            if(sum <= 3) {
                puts("NO");
                return;
            } else {
                puts("YES");
                printf("%d %d %d
    ", fac[1][0], fac[2][0], n / fac[1][0] / fac[2][0]);
                return;
            }
        }
    }
    

    D - MEX maximizing

    题意:有q次操作和一个固定的正整数x,每次操作会往序列中加入一个数字y,你可以在任何时候对序列中的某个y进行任意多次加减x操作(但不能使他们变成负数),求使得整个序列的MEX最大的方案时的MEX的值。

    题解:显然模x余数相同的都是同一种等价的东西,可以维护模x值不同结果的cnt。那么MEX操作就相当于询问整个cnt序列中的最小值(这个最小值是一层一层循环叠在下面把MEX垫高),然后再求序列中等于这个最小值的最左边的元素,这个显然可以用线段树来维护(甚至可以支持修改删除)。

    但是最简单的操作还是在cnt里面数了之后不断尝试越过MEX即可。

    不知道为什么这个操作恰好就是可以用pair的默认操作来维护,但是换成pair之后会编译失败。

    struct SegmentTree {
    #define ls (o<<1)
    #define rs (o<<1|1)
        static const int MAXN = 420000;
        int st[(MAXN << 2) + 5];
        int pos[(MAXN << 2) + 5];
     
        void PushUp(int o) {
            st[o] = min(st[ls], st[rs]);
            if(st[rs] < st[ls])
                pos[o] = pos[rs];
            else
                pos[o] = pos[ls];
        }
     
        void Build(int o, int l, int r) {
            if(l == r) {
                st[o] = 0;
                pos[o] = l;
            } else {
                int m = l + r >> 1;
                Build(ls, l, m);
                Build(rs, m + 1, r);
                PushUp(o);
            }
        }
     
        void Update(int o, int l, int r, int p, int v) {
            if(l == r) {
                st[o] += v;
                return;
            } else {
                int m = l + r >> 1;
                if(p <= m)
                    Update(ls, l, m, p, v);
                if(p >= m + 1)
                    Update(rs, m + 1, r, p, v);
                PushUp(o);
            }
        }
     
        pii QueryMin(int o, int l, int r, int ql, int qr) {
            if(ql <= l && r <= qr) {
                return {st[o], pos[o]};
            } else {
                int m = l + r >> 1;
                int res = INF;
                int resp = -1;
                if(ql <= m) {
                    pii tmp = QueryMin(ls, l, m, ql, qr);
                    res = tmp.first;
                    resp = tmp.second;
                }
                if(qr >= m + 1) {
                    pii tmp = QueryMin(rs, m + 1, r, ql, qr);
                    if(tmp.first < res) {
                        res = tmp.first;
                        resp = tmp.second;
                    }
                }
                return {res, resp};
            }
        }
    #undef ls
    #undef rs
    } st;
     
    void test_case() {
        int q, x;
        scanf("%d%d", &q, &x);
        st.Build(1, 1, x);
        while(q--) {
            int y;
            scanf("%d", &y);
            y = (y % x) + 1;
            st.Update(1, 1, x, y, 1);
            pii tmp = st.QueryMin(1, 1, x, 1, x);
            printf("%d
    ", tmp.first * x + (tmp.second - 1));
        }
    }
    

    E - Obtain a Permutation

    题意:给一个n*m的矩阵,要求复原成形如:

    1  2  3  4
    5  6  7  8
    9  10 11 12
    

    的样子,也就是依次填充。

    有两种cost都是1的操作,1种是直接修改某位置的数字,另一种是把一列向上轮换。

    首先,列与列之间显然是独立的,可以分开考虑,那么就考虑某列怎么复原。最简单的方法就是枚举这一列的开头在哪一行(也就是枚举需要向上),然后每个元素就知道是否对应当前是否处在正确的位置上,每个不在正确位置上的都必须要被换掉。这个复杂度是(单列)n^2的。观察一下容易发现每个元素最多在某次枚举中处在正确位置,这个关系要从等差数列公式推一推。

    然后这样做只会会RE,因为访问到了负数。然后修复这个bug只会会WA,想一想其实还会上溢出,因为题目给的数字不一定在n*m以内,这样子确实还是会溢出。

    vector<int> a[200005];
    int pos[200005];
    
    void test_case() {
        int n, m;
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; ++i) {
            a[i].resize(m + 5);
            for(int j = 1; j <= m; ++j)
                scanf("%d", &a[i][j]);
        }
        ll sumcost = 0;
        int C = n * m;
        for(int j = 1; j <= m; ++j) {
            int mincost = INF;
            for(int i = 0; i < n; ++i)
                pos[i] = 0;
            for(int i = 1; i <= n; ++i) {
                if(a[i][j] > C || (a[i][j] - j) % m != 0)
                    continue;
                else {
                    int id = i - 1 - ((a[i][j] - j) / m);
                    if(id < 0)
                        id += n;
                    ++pos[id];
                }
            }
            for(int i = 0; i < n; ++i)
                mincost = min(mincost, n - pos[i] + i);
            sumcost += mincost;
        }
        printf("%lld
    ", sumcost);
    }
    

    F - Three Paths on a Tree

    题意:给一棵无权树,选三个点,使得他们之间的简单路径覆盖的边数最多。

    题解:在树上上面选三个点,他们的路径必然也是一棵树,所以要求边数最多就是点数最多。其实很显然其中两个点必然是某一条直径的两端。首先,三个点都是叶子,因为若不是叶子,则把这个节点换成某个方向的叶子,一定更好。然后其中两个点必然是某一条直径的两端。否则,把其中两个点换成直径,会更好。所以只需要找出直径,然后找一个到直径距离最远的点。使用多源bfs即可。官方题解提到可以使用dp,仔细想想确实,设dp[i][j]为以1为根时,i的子树内选择j个点,覆盖的边的最大值,因为每次只会和当前子树的根有关所以满足dp的性质。简而言之:dp[u][0]=0,dp[u][1]=max(dp[v][1]+1),dp[u][2]=max(max(dp[v][2]+1),max(dp[v1][1]+dp[v2][1]+1)),dp[u][3]=max(max(dp[v][3]),max(dp[v1][2]+dp[v2][1]+1),max(dp[v1][1]+dp[v2][1]+dp[v3][1]+1))。

    使用多源bfs,注意树为一条链的情况,此情况不存在“到直径距离最大且不不为0”的点,故把r3的初始值设为pa[r2],这样就算不更新答案也不会错。

    int n;
    vector<int> G[200005];
    int pa[200005];
    int dep[200005];
    
    void dfs(int u, int p) {
        pa[u] = p;
        dep[u] = dep[p] + 1;
        for(auto &v : G[u]) {
            if(v == p)
                continue;
            dfs(v, u);
        }
    }
    
    int r1, r2, r3;
    
    queue<int> Q;
    int dis[200005];
    void bfs() {
        while(!Q.empty())
            Q.pop();
        for(int i = 1; i <= n; ++i)
            dis[i] = -1;
        int ans = 0;
        int x = r2;
        r3 = pa[r2];
        while(x) {
            ++ans;
            Q.push(x);
            dis[x] = 0;
            x = pa[x];
        }
        while(!Q.empty()) {
            int u = Q.front();
            Q.pop();
            for(auto &v : G[u]) {
                if(dis[v] != -1)
                    continue;
                dis[v] = dis[u] + 1;
                Q.push(v);
            }
        }
        for(int i = 1; i <= n; ++i) {
            if(dis[i] > dis[r3])
                r3 = i;
        }
        ans += dis[r3];
        printf("%d
    %d %d %d
    ", ans - 1, r1, r2, r3);
    }
    
    void test_case() {
        scanf("%d", &n);
        for(int i = 1; i <= n - 1; ++i) {
            int u, v;
            scanf("%d%d", &u, &v);
            G[u].push_back(v);
            G[v].push_back(u);
        }
        dfs(1, 0);
        r1 = 0;
        for(int i = 1; i <= n; ++i) {
            if(dep[i] > dep[r1])
                r1 = i;
        }
        dfs(r1, 0);
        r2 = 0;
        for(int i = 1; i <= n; ++i) {
            if(dep[i] > dep[r2])
                r2 = i;
        }
        bfs();
    }
    

    反思:分类讨论要仔细。注意构造的几个点会不会发生重合,而且把答案的初始值设为某个“合理”答案而不是非法值“0”、“-1”,虽然在debug阶段可能不容易看出问题,但是假如真的是有问题的话,鲁棒性确实会好一些。

  • 相关阅读:
    cordova 日曆 聯系人 插件使用
    ionic 不同view的數據交互
    JQuery iframe 刷新效果
    fis学习
    如何用nfs命令烧写内核和文件系统(网络下载文件到nandflash)(未完)
    布线的基本原则
    电平转转换电路
    焊盘的制作
    flash-热风焊盘的制作
    焊盘的层面剖析
  • 原文地址:https://www.cnblogs.com/KisekiPurin2019/p/12230102.html
Copyright © 2020-2023  润新知