zoukankan      html  css  js  c++  java
  • Codeforces Round #629 (Div. 3)

    题目链接:https://codeforces.com/contest/1328

    A - Divisibility Problem

    很古老的一个idea,送分题。

    B - K-th Beautiful String

    题意:要求构造一个由 (n-2) 个'a'和 (2) 个'b'构成的字符串,显然这样的字符串有 (C_{n}^{2}) 种,找出其中字典序第 (k) 小的答案。

    题解:线性枚举第一个'b'的位置,很快可以找到第 (k) 小需要的第一个'b'在哪,然后直接计算出第二个'b'在哪。假如不是要输出整个字符串的话,应该有更快的做法,比如log的做法:计算第一个'b'在倒数第 (i) 个的时候,有多少种串,这个显然和 (n) 无关,可以重复用,然后在前缀和上面二分。或者有个接近线性的做法:因为前面这个前缀和是个等差数列求和,必然是个二次形式的东西,可以通过解方程(开平方)求出 (k) 大概对应第一个'b'在哪个位置,然后在这个附近开始找就可以了。

    C - Ternary XOR

    这个东西这么难,学弟居然会。

    题意:给一个长度为 (n) 的字符串 (s) ,只有'0','1','2'三种数字,保证最左侧一定是'2',求构造两个字符串 (a)(b) ,使得 (a oplus b) 恰好为 (s) ,定义三进制的异或 (oplus)((a_i+b_i)mod 3) 。且他们代表的数字 (max(a,b)) 最小。

    题解:一开始觉得直接把所有的'2'换成两个'1',把'1'换成'1'和'0',把'0'换成两个'0'。但是仔细想想假如有多个'1'的话,应该是第一个'1'是分成'1'和'0',剩下的'1'分成'0'和'1',然后又注意到假如前面出现了一个'1'之后,就必定存在一个大数和一个小数,这时'2'要分解为'0'和'2',也就是说,假如没有任何'1',那么对半分就是最好的结果。否则把第一个'1'分给 (a) ,然后 (a) 后面就可以全部接'0'了,因为这时候无论 (b) 接什么都不可能比 (a) 大。

    题意:给一个环,环上的节点有一些数,求一种用的颜色数最少的染色方案,使得环上的这些数中,所有的相邻位置且数字不同的二元组,都染上不同的颜色。

    题解:首先,假如所有的数字都相同,那么肯定只需要1种颜色。否则,若至少有两种数字,那么在数字切换的位置就至少需要2种颜色。然后,显然假如环的长度是偶数,最大的颜色数就是2种,那么染色方案"12121212"就是一种合法的解,因为相邻位置都不同颜色,根本不管是不是数字不同。根据这个构造,可以看出来,假如环的长度是奇数,最大的颜色就是3种,因为"121212123"就是一种合法的构造,因为相邻位置都不同颜色,根本不管是不是数字不同。那么会不会有2种颜色的方法呢?继续按照这个奇偶染色的思路走,因为是奇数长度,所以只需要让有一个连续位置染成同色即可,注意到染成同色的,必须是连续的相同数字,所以就找是否存在这一的连续相同数字,找到第一个就break出来,注意特殊处理:第一个就连续相同、首尾连续相同,两种比较特别的情况。

    int a[200005];
    int b[200005];
     
    void TestCase() {
        int n;
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i)
            scanf("%d", &a[i]);
        a[n + 1] = a[1];
        bool allsame = 1;
        for(int i = 1; i <= n; ++i) {
            if(a[i] != a[i + 1]) {
                allsame = 0;
                break;
            }
        }
        if(allsame) {
            //没有连续两个元素是相同的,全部染1
            puts("1");
            for(int i = 1; i <= n; ++i)
                printf("%d%c", 1, " 
    "[i == n]);
            return;
        }
        //至少有一个位置,是连续两个元素相同,至少是2
        if(n % 2 == 0) {
            //是偶数
            puts("2");
            for(int i = 1; i <= n; ++i)
                printf("%d%c", 2 - (i & 1), " 
    "[i == n]);
            return;
        }
        //是奇数
        //假如有连续两个元素是相同的,就染成同色,然后剩下的依然是间隔
        int pos = -1;
        for(int i = 1; i <= n; ++i) {
            if(a[i] == a[i + 1]) {
                pos = i;
                break;
            }
        }
        if(pos == -1) {
            //不存在,至少需要3种
            puts("3");
            for(int i = 1; i <= n - 1; ++i)
                printf("%d ", 2 - (i & 1));
            puts("3");
            return;
        }
        //存在,则把这两个位置染成同色
        b[0] = 2;
        puts("2");
        if(pos != n) {
            for(int i = 1; i < pos; ++i)
                b[i] = 2 - (i & 1);
            b[pos] = b[pos + 1] = 3 - b[pos - 1];
            for(int i = pos + 2; i <= n; ++i)
                b[i] = 3 - b[i - 1];
            for(int i = 1; i <= n; ++i)
                printf("%d%c", b[i], " 
    "[i == n]);
            return;
        } else {
            for(int i = 1; i <= n - 1; ++i)
                printf("%d ", 2 - (i & 1));
            puts("1");
            return;
        }
    }
    

    *E - Tree Queries

    好假哦,我在干什么。

    标签:树上倍增,LCA

    题意:给一棵 (n) 个节点的树,然后若干次询问,第 (i) 次询问一个大小为 (k_i) 的点集 (v_1,v_2,...,v_{k_i}) ,问是否存在根节点到某个节点的路径,使得这个点集到路径的距离不是0就是1。所有询问的点集大小的总和与 (n) 同阶。

    题解:首先想到一个贪心,就是选出的节点必定是叶子,但是总不能预处理每个叶子对应覆盖的点集吧?甚至连每个叶子对应的到根节点的路径都不能存下来。然后又发现,假如有若干个深度最大的节点,那么他们必须要有同一个父亲 (p) ,否则无解。经过这个观察之后,就知道一定要选根节点到 (p) 的路径,而且要求点集中的点都要在路径上,或者其父亲在路径上。再观察一下假如把根节点的父亲定义为自身,那么只需要所有点的父亲都在路径上,当时就写了一个预处理这个路径进入set,然后从中查询所有点的父亲的算法。TLE58了,当时还以为是哪里死循环了,仔细看了所有的循环都不觉得有问题,就觉得为什么nlogn的算法会不行,改成用个vis数组打标记来替代set。结果还真变快了,变成TLE76了,加个快读还是TLE76。

    vector<int> G[200005];
    int dep[200005];
    int pa[200005];
     
    void dfs(int u, int p) {
        pa[u] = p;
        if(u != p)
            dep[u] = dep[p] + 1;
        for(auto &v : G[u]) {
            if(v == p)
                continue;
            dfs(v, u);
        }
        return;
    }
     
    int v[200005];
    set<int> SET;
     
    void solve() {
        int k;
        scanf("%d", &k);
        int maxdep = 0;
        for(int i = 1; i <= k; ++i) {
            scanf("%d", &v[i]);
            if(dep[v[i]] > maxdep)
                maxdep = dep[v[i]];
        }
        int gp = -1;
        for(int i = 1; i <= k; ++i) {
            if(dep[v[i]] == maxdep) {
                if(gp == -1)
                    gp = pa[v[i]];
                else {
                    if(pa[v[i]] != gp) {
                        puts("NO");
                        return;
                    }
                }
            }
        }
        SET.clear();
        SET.insert(1);
        while(gp != 1) {
            SET.insert(gp);
            gp = pa[gp];
        }
        for(int i = 1; i <= k; ++i) {
            if(SET.count(pa[v[i]]) == 0) {
                puts("NO");
                return;
            }
        }
        puts("YES");
        return;
    }
     
    void TestCase() {
        int n, m;
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; ++i)
            G[i].clear();
        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);
        }
        dep[1] = 1;
        dfs(1, 1);
        while(m--)
            solve();
        return;
    }
    
    vector<int> G[200005];
    int dep[200005];
    int pa[200005];
     
    void dfs(int u, int p) {
        pa[u] = p;
        if(u != p)
            dep[u] = dep[p] + 1;
        for(auto &v : G[u]) {
            if(v == p)
                continue;
            dfs(v, u);
        }
        return;
    }
     
    int v[200005];
    int vis[200005];
     
    int read() {
        int res = 0;
        char c = getchar();
        while(!isdigit(c))
            c = getchar();
        while(isdigit(c)) {
            res = res * 10 + c - '0';
            c = getchar();
        }
        return res;
    }
     
    void solve() {
        int k;
        k = read();
        int maxdep = 0;
        for(int i = 1; i <= k; ++i) {
            v[i] = read();
            if(dep[v[i]] > maxdep)
                maxdep = dep[v[i]];
        }
        int gp = -1;
        for(int i = 1; i <= k; ++i) {
            if(dep[v[i]] == maxdep) {
                if(gp == -1)
                    gp = pa[v[i]];
                else {
                    if(pa[v[i]] != gp) {
                        puts("NO");
                        return;
                    }
                }
            }
        }
        int tgp = gp;
        vis[1] = 1;
        while(gp != 1) {
            vis[gp] = 1;
            gp = pa[gp];
        }
        bool suc = 1;
        for(int i = 1; i <= k; ++i) {
            if(!vis[pa[v[i]]]) {
                suc = 0;
                break;
            }
        }
        vis[1] = 0;
        while(tgp != 1) {
            vis[tgp] = 0;
            tgp = pa[tgp];
        }
        if(suc)
            puts("YES");
        else
            puts("NO");
        return;
    }
     
    void TestCase() {
        int n, m;
        n = read(), m = read();
        for(int i = 1; i <= n; ++i)
            G[i].clear();
        for(int i = 1; i <= n - 1; ++i) {
            int u, v;
            u = read(), v = read();
            G[u].push_back(v);
            G[v].push_back(u);
        }
        dep[1] = 1;
        dfs(1, 1);
        while(m--)
            solve();
        return;
    }
    

    TLE了3次之后,突然意识到,这样预处理整条链,单次的复杂度可以到 (O(n)) ,整个复杂度是 (O(n^2)) 的,看来我玄学优化加太多了导致这样都可以过75组数据。然后想了想,应该把这些点按深度排序,然后每次取相邻的两个点的父亲验证他们的祖先后代关系,假如不是祖先后代,就说明NO,否则就跳到那个祖先节点继续走,假如一直都是祖先,那么把这些祖先连成一条链就是答案。

    vector<int> G[200005];
    int dep[200005];
    int fa[200005][20 + 1];
     
    void dfs(int u, int p) {
        dep[u] = dep[p] + 1;
        fa[u][0] = p;
        for (int i = 1; i <= 20; i++)
            fa[u][i] = fa[fa[u][i - 1]][i - 1];
        for(auto &v : G[u]) {
            if(v == p)
                continue;
            dfs(v, u);
        }
    }
     
    int read() {
        int res = 0;
        char c = getchar();
        while(!isdigit(c))
            c = getchar();
        while(isdigit(c)) {
            res = res * 10 + c - '0';
            c = getchar();
        }
        return res;
    }
     
    pii v2[200005];
     
    void solve() {
        int k;
        k = read();
        for(int i = 1; i <= k; ++i) {
            int x = read();
            v2[i] = {dep[x], x};
        }
        if(k == 1) {
            puts("YES");
            return;
        }
        sort(v2 + 1, v2 + 1 + k);
        for(int i = 2; i <= k; ++i) {
            if(v2[i].first == v2[i - 1].first) {
                if(fa[v2[i].second][0] != fa[v2[i - 1].second][0]) {
                    puts("NO");
                    return;
                }
            }
        }
        int cur = v2[k].second;
        for(int i = k - 1; i >= 1; --i) {
            int x = v2[i].second;
            for(int j = 20; j >= 0; --j) {
                if(dep[fa[cur][j]] >= dep[x])
                    cur = fa[cur][j];
            }
            assert(dep[cur] == dep[x]);
            if(fa[cur][0] != fa[x][0]) {
                puts("NO");
                return;
            }
        }
        puts("YES");
        return;
    }
     
    void TestCase() {
        int n, m;
        n = read(), m = read();
        for(int i = 1; i <= n; ++i)
            G[i].clear();
        for(int i = 1; i <= n - 1; ++i) {
            int u, v;
            u = read(), v = read();
            G[u].push_back(v);
            G[v].push_back(u);
        }
        dfs(1, 0);
        while(m--)
            solve();
        return;
    }
    

    应该还有办法优化,可以根据深度差的信息找到要跳的log的上限(类似ST表的预处理)。但是实际跑起来却更慢。

    补充:看了一下官方题解,学到一些新的套路:判断祖先后辈关系甚至不需要用log的树上倍增法,注意在dfs中打上in时间和out时间,祖先后辈关系的in和out时间必定是嵌套的,这个只需要扫一遍就可以得到答案。甚至连排序都不需要,只需要所有的in出现之后再出现out就可以了。

    struct Edge {
        int v, nxt;
    } edge[400005];
    int head[200005], top;
    
    int pa[200005];
    int intime[200005];
    int outtime[200005];
    int t;
    
    void dfs(int u, int p) {
        pa[u] = p;
        intime[u] = ++t;
        for(int i = head[u]; i; i = edge[i].nxt) {
            int v = edge[i].v;
            if(v == p)
                continue;
            dfs(v, u);
        }
        outtime[u] = ++t;
    }
    
    int read() {
        int res = 0;
        char c = getchar();
        while(!isdigit(c))
            c = getchar();
        while(isdigit(c)) {
            res = res * 10 + c - '0';
            c = getchar();
        }
        return res;
    }
    
    void solve() {
        int k;
        k = read();
        int maxin = -INF, minout = INF;
        for(int i = 1; i <= k; ++i) {
            int x = read();
            x = x == 1 ? x : pa[x];
            maxin = max(maxin, intime[x]);
            minout = min(minout, outtime[x]);
        }
        if(maxin < minout)
            puts("YES");
        else
            puts("NO");
        return;
    }
    
    void TestCase() {
        int n, m;
        n = read(), m = read();
        top = 0;
        for(int i = 1; i <= n - 1; ++i) {
            int u, v;
            u = read(), v = read();
            ++top;
            edge[top].v = v;
            edge[top].nxt = head[u];
            head[u] = top;
            ++top;
            edge[top].v = u;
            edge[top].nxt = head[v];
            head[v] = top;
        }
        t = 0;
        dfs(1, 0);
        while(m--)
            solve();
        return;
    }
    

    *F - Make k Equal

    题意:给 (n) 个数,要求使用最少的操作,使得至少有 (k) 个数相同。每次操作:要么把某个等于最大值的数-1.要么把某个等于最小值的数+1。

    题解:需要注意到一点,就是最后这些相同的数,总是可以在初始给的数字之中,这个和lyd的书上面给的情况一样。那么枚举每个值 (v=a[i]) 作为最后的相同的 (k) 个数的情况,首先特判掉本身就有 (k) 个数相同的值的情况,然后要注意到:

    假如要把小于 (v) 的数的一部分变成 (v) ,首先要全部变成 (v-1) 。然后再选一部分变成 (v) ,而不是直接全部变成 (v)
    假如要把大于 (v) 的数的一部分变成 (v) ,首先要全部变成 (v+1) 。然后再选一部分变成 (v) ,而不是直接全部变成 (v)

    也就是说,增加 (v) 的个数的办法,要么途径 (v-1) ,要么途径 (v+1) ,或者两者都是。

    假如是“两者都是”,就把其他的数全部变成 (v-1) 或者 (v+1) ,然后任选其中的 (k-cnt[v]) 个即可。

    假如是“只有一侧”,先确认这一侧的数有至少 (k) 个,然后就把这一侧的数全部变成 (v-1) 或者 (v+1) ,然后任选其中的 (k-cnt[v]) 个即可。

    小心乘法溢出,看清楚括号的结合顺序,或者干脆全部用ll就不会出事。

    int a[200005];
    int x[200005];
    int cnt[200005];
    int sumcnt[200005];
    ll sum[200005];
    ll pre[200005];
    ll suf[200005];
    
    void TestCase() {
        int n, k;
        scanf("%d%d", &n, &k);
        for(int i = 1; i <= n; ++i) {
            scanf("%d", &a[i]);
            x[i] = a[i];
        }
        sort(x + 1, x + 1 + n);
        int m = unique(x + 1, x + 1 + n) - (x + 1);
        for(int i = 1; i <= n; ++i) {
            int pos = lower_bound(x + 1, x + 1 + m, a[i]) - x;
            ++cnt[pos];
            if(cnt[pos] >= k) {
                puts("0");
                return;
            }
        }
        for(int i = 1; i <= m; ++i) {
            sumcnt[i] = sumcnt[i - 1] + cnt[i];
            sum[i] = sum[i - 1] + 1ll * cnt[i] * x[i];
        }
        for(int i = 1; i <= m; ++i) {
            pre[i] = 1ll * sumcnt[i - 1] * (x[i] - 1) - sum[i - 1];
            suf[i] = (sum[m] - sum[i]) - 1ll * (n - sumcnt[i]) * (x[i] + 1);
        }
    
        /*for(int i = 1; i <= m; ++i) {
            printf("i=%d x=%d cnt=%d
    ", i, x[i], cnt[i]);
            printf("  sumcnt=%d sum=%lld
    ", sumcnt[i], sum[i]);
            printf("  pre=%lld suf=%lld
    ", pre[i], suf[i]);
        }*/
    
        ll ans = LINF;
        for(int i = 1; i <= m; ++i) {
            ll tmp1 = pre[i] + suf[i] + k - cnt[i];
            ans = min(ans, tmp1);
            if(sumcnt[i] >= k) {
                ll tmp2 = pre[i] + k - cnt[i];
                ans = min(ans, tmp2);
            }
            if(n - sumcnt[i - 1] >= k) {
                ll tmp3 = suf[i] + k - cnt[i];
                ans = min(ans, tmp3);
            }
            //printf("i=%d ans=%lld
    ", i, ans);
        }
        printf("%lld
    ", ans);
        return;
    }
    

    为什么unique不是把元素移动到末尾呢?

  • 相关阅读:
    redis集群学习
    Java -cp 命令行引用多个jar包的简单写法(Windows、Linux
    内部类的继承
    NIO的epoll空轮询bug
    linux下socket的连接队列的 backlog的分析
    jQuery animate动画 stop()方法详解~
    jQuery插件之Cookie插件使用方法~
    jQuery中 pageX,clientX,offsetX,layerX的区别
    JavaScript 中一些概念理解 :clientX、clientY、offsetX、offsetY、screenX、screenY
    jQuery $.fn 方法扩展~
  • 原文地址:https://www.cnblogs.com/KisekiPurin2019/p/12579515.html
Copyright © 2011-2022 走看看