• Codeforces Round #624 (Div. 3)


    A - Add Odd or Subtract Even

    诚心诚意的签到题。不过做的时候有点启发,也就是(a-b)%2的结果,可以是-1,0,+1。

    B - WeirdSort

    题意:给一个数组,以及指定其中一些位置是可以进行邻位交换的,可以交换任意多次。求是否能把数组变回有序。

    题解:数据量太小,甚至可以检查每个可交换的位置是否需要交换。但是更明显的是一段连续的可邻位交换的位置可以直接sort。注意sort传入的下标的格式。

    int a[1005];
    int p[1005];
    
    void test_case() {
        int n, m;
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; ++i)
            scanf("%d", &a[i]);
        for(int i = 1; i <= m; ++i)
            scanf("%d", &p[i]);
        sort(p + 1, p + 1 + m);
        for(int l = 1, r = 1; l <= m; l = r + 1, r = l) {
            while(r + 1 <= m && p[r + 1] == p[r] + 1)
                ++r;
            sort(a + p[l], a + p[r] + 1 + 1);
        }
        for(int i = 2; i <= n; ++i) {
            if(a[i] < a[i - 1]) {
                puts("NO");
                return;
            }
        }
        puts("YES");
        return;
    }
    

    C - Perform the Combo

    诚心诚意的签到题。比B还水。

    *D - Three Integers

    题意:给三个数a,b,c(1<=a,b,c<=1e4),用最少的操作使得a<=b<=c且a是b的因数且b是c的因数,每次操作可以对一个数进行+1或者-1(需要保证结果仍然是正数)。

    题解:发现b是对两边进行制约的,假如枚举b的取值,可以立刻算出c的取值,但是a的取值比较难算,要分解因数。但是假如反过来dp,枚举a的取值,然后把a的倍数全部更新,复杂度会显著降低。易知枚举的上限是2e4,因为把三个数变成完全相等,需要的代价最大也是1e4(max减去min的值就是1e4,min和max都往中间的数靠,总共至多移动1e4次,所以b最大变到2e4)。

    int dis[20005];
    int from[20005];
    
    void test_case() {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        memset(dis, INF, sizeof(dis));
        for(int i = 1; i <= 20000; ++i) {
            int d = abs(a - i);
            for(int j = i; j <= 20000; j += i) {
                if(d < dis[j]) {
                    dis[j] =  d;
                    from[j] = i;
                }
            }
        }
        int ans = INF, ansa, ansb, ansc;
        for(int i = 1; i <= 20000; ++i) {
            int tmpans = dis[i];
            tmpans += abs(b - i);
            tmpans += (c <= i) ? (i - c) : min(c % i, i - c % i);
            if(ans > tmpans) {
                ans = tmpans;
                ansa = from[i];
                ansb = i;
                if(c <= i)
                    ansc = i;
                else if(c % i <= i - c % i)
                    ansc = c / i * i;
                else
                    ansc = (c / i + 1) * i;
            }
        }
        printf("%d
    ", ans);
        printf("%d %d %d
    ", ansa, ansb, ansc);
    }
    

    *E - Construct the Binary Tree

    又一条2400的题,是不是准备要起飞了?

    题意:构造一棵以1为根的n个节点的二叉树,使得其所有节点的深度和恰好为d。

    题解:易知最小深度和为完全二叉树,最大深度和为一条链,判断d不超过最大深度和之后,可以逐步调整。每次调整贪心把深度最大的节点往上调整,若有某个位置刚刚好,就调整到那个位置,否则调整到当前最小深度的空位上。易知这样会产生两个新的比当前最小深度+1的空位。假如已经形成完全二叉树(当前最小深度的空位的深度已经不小于(不是等于)最大深度的节点的深度了)。每个节点至多被调整1次,所以复杂度与n同阶(比官方题解更好)。

    stack<int> dep[5005];
    int pa[5005];
    
    void test_case() {
        int n, d;
        scanf("%d%d", &n, &d);
        if(d > n * (n - 1) / 2) {
            puts("NO");
            return;
        }
        for(int i = 1; i <= n; ++i) {
            while(!dep[i].empty())
                dep[i].pop();
        }
        int curd = n * (n - 1) / 2;
        for(int i = 2; i <= n; ++i) {
            pa[i] = i - 1;
            dep[i - 1].push(i - 1);
        }
        int mind = 1, maxd = n - 1;
        while(1) {
            if(curd == d) {
                puts("YES");
                for(int i = 2; i <= n; ++i)
                    printf("%d%c", pa[i], " 
    "[i == n]);
                return;
            }
            if(mind >= maxd) {
                puts("NO");
                return;
            }
            if(curd - d <= maxd - mind) {
                pa[maxd + 1] = dep[maxd - (curd - d)].top();
                puts("YES");
                for(int i = 2; i <= n; ++i)
                    printf("%d%c", pa[i], " 
    "[i == n]);
                return;
            }
            pa[maxd + 1] = dep[mind].top();
            dep[mind].pop();
            dep[mind + 1].push(maxd + 1);
            dep[mind + 1].push(maxd + 1);
            curd -= maxd - mind;
            if(dep[mind].empty())
                ++mind;
            --maxd;
        }
    }
    

    F - Moving Points

    看通过率觉得可能很水,结果还真的很水。

    题意:有n个点,第i个点初始在xi,并且有恒定的速度vi,定义两个点之间的最小距离为从0时刻开始的两点的距离的最小值。求所有点对之间的最小距离的和。

    题解:把所有点按xi排序,维护两棵平衡树,把xj小于当前的xi的全部放在左树,把xj大于当前的xi的全部放在右树,在左树中找速度<=vi的点的距离和,在右树中找速度>=vi的点的距离和,按xi的顺序扫描,逐个把右树的点搬到左树,即可。上面这个算法恰好把同一个距离算了两倍,其实只需要保留一棵右树,就可以算出来。

    struct Treap {
    #define ls ch[id][0]
    #define rs ch[id][1]
        static const int MAXN = 200000;
        int ch[MAXN + 5][2], dat[MAXN + 5];
    
        int val[MAXN + 5];
        ll val2[MAXN + 5];
        ll sumval2[MAXN + 5];
        int cnt[MAXN + 5];
        int sumcnt[MAXN + 5];
    
        int tot, root;
    
        inline void Init() {
            tot = 0;
            root = 0;
        }
    
        inline int NewNode(int v, ll x) {
            int id = ++tot;
            ls = rs = 0;
            dat[id] = rand();
            val[id] = v;
            val2[id] = x;
            sumval2[id] = x;
            cnt[id] = 1;
            sumcnt[id] = 1;
            return id;
        }
    
        inline void PushUp(int id) {
            sumval2[id] = sumval2[ls] + sumval2[rs] + val2[id];
            sumcnt[id] = sumcnt[ls] + sumcnt[rs] + cnt[id];
        }
    
        inline void Rotate(int &id, int d) {
            int temp = ch[id][d ^ 1];
            ch[id][d ^ 1] = ch[temp][d];
            ch[temp][d] = id;
            id = temp;
            PushUp(ch[id][d]);
            PushUp(id);
        }
    
        inline void Insert(int &id, int v, ll x) {
            if(!id)
                id = NewNode(v, x);
            else {
                if(v == val[id]) {
                    val2[id] += x;
                    cnt[id] += 1;
                } else {
                    int d = val[id] > v ? 0 : 1;
                    Insert(ch[id][d], v, x);
                    if(dat[id] < dat[ch[id][d]])
                        Rotate(id, d ^ 1);
                }
                PushUp(id);
            }
        }
    
        void Remove(int &id, int v, ll x) {
            if(!id)
                return;
            else {
                if(v == val[id]) {
                    if(val2[id] > x) {
                        val2[id] -= x;
                        cnt[id] -= 1;
                        PushUp(id);
                    } else if(ls || rs) {
                        if(!rs || dat[ls] > dat[rs])
                            Rotate(id, 1), Remove(rs, v, x);
                        else
                            Rotate(id, 0), Remove(ls, v, x);
                        PushUp(id);
                    } else
                        id = 0;
                } else {
                    val[id] > v ? Remove(ls, v, x) : Remove(rs, v, x);
                    PushUp(id);
                }
            }
        }
    
        //查询小于等于v的数的和
        ll GetSumValue2LEQ(int id, int v) {
            ll res = 0;
            while(id) {
                if(val[id] > v)
                    id = ls;
                else if(val[id] == v) {
                    res += sumval2[ls] + val2[id];
                    break;
                } else {
                    res += sumval2[ls] + val2[id];
                    id = rs;
                }
            }
            return res;
        }
    
        //查询小于等于v的数的数量
        int GetSumCntLEQ(int id, int v) {
            int res = 0;
            while(id) {
                if(val[id] > v)
                    id = ls;
                else if(val[id] == v) {
                    res += sumcnt[ls] + cnt[id];
                    break;
                } else {
                    res += sumcnt[ls] + cnt[id];
                    id = rs;
                }
            }
            return res;
        }
    
        //查询大于等于v的数的和
        ll GetSumValue2GEQ(int id, int v) {
            ll res = 0;
            while(id) {
                if(val[id] > v) {
                    res += sumval2[rs] + val2[id];
                    id = ls;
                } else if(val[id] == v) {
                    res += sumval2[rs] + val2[id];
                    break;
                } else
                    id = rs;
            }
            return res;
        }
    
        //查询大于等于v的数的数量
        int GetSumCntGEQ(int id, int v) {
            int res = 0;
            while(id) {
                if(val[id] > v) {
                    res += sumcnt[rs] + cnt[id];
                    id = ls;
                } else if(val[id] == v) {
                    res += sumcnt[rs] + cnt[id];
                    break;
                } else
                    id = rs;
            }
            return res;
        }
    #undef ls
    #undef rs
    }Right;
    
    pii p[200005];
    
    void test_case() {
        int n;
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i)
            scanf("%d", &p[i].first);
        for(int i = 1; i <= n; ++i)
            scanf("%d", &p[i].second);
        sort(p + 1, p + 1 + n);
        ll ans = 0;
        Right.Init();
        for(int i = 1; i <= n; ++i)
            Right.Insert(Right.root, p[i].second, p[i].first);
        for(int i = 1; i <= n; ++i) {
            ans += Right.GetSumValue2GEQ(Right.root, p[i].second)
                   - 1ll * p[i].first * Right.GetSumCntGEQ(Right.root, p[i].second);
            Right.Remove(Right.root, p[i].second, p[i].first);
        }
        printf("%lld
    ", ans);
    }
    

    看了别人的代码,发现其实可以用树状数组来做,因为只关心大于等于某个v值的所有还在树中的点的x值的和,可以反过来变成一个前缀和。

    启示:可以离线排序关键字的算法,不需要使用平衡树,使用树状数组或者线段树即可,当询问的区间为>=x或者<=x这样的形式时,尤其适合树状数组。

    struct SegmentTree {
    #define ls (o<<1)
    #define rs (o<<1|1)
        static const int MAXN = 200000;
        ll sumx[(MAXN << 2) + 5];
        int cnt[(MAXN << 2) + 5];
    
        void PushUp(int o) {
            sumx[o] = sumx[ls] + sumx[rs];
            cnt[o] = cnt[ls] + cnt[rs];
        }
    
        void Build(int o, int l, int r) {
            if(l == r) {
                sumx[o] = 0;
                cnt[o] = 0;
            } else {
                int m = l + r >> 1;
                Build(ls, l, m);
                Build(rs, m + 1, r);
                PushUp(o);
            }
        }
    
        void Update1(int o, int l, int r, int p, ll v) {
            if(l == r) {
                sumx[o] += v;
                cnt[o] += 1;
                return;
            } else {
                int m = l + r >> 1;
                if(p <= m)
                    Update1(ls, l, m, p, v);
                if(p >= m + 1)
                    Update1(rs, m + 1, r, p, v);
                PushUp(o);
            }
        }
    
        void Update2(int o, int l, int r, int p, ll v) {
            if(l == r) {
                sumx[o] -= v;
                cnt[o] -= 1;
                return;
            } else {
                int m = l + r >> 1;
                if(p <= m)
                    Update2(ls, l, m, p, v);
                if(p >= m + 1)
                    Update2(rs, m + 1, r, p, v);
                PushUp(o);
            }
        }
    
        ll QuerySumx(int o, int l, int r, int ql, int qr) {
            if(ql <= l && r <= qr) {
                return sumx[o];
            } else {
                int m = l + r >> 1;
                ll res = 0;
                if(ql <= m)
                    res = res + QuerySumx(ls, l, m, ql, qr);
                if(qr >= m + 1)
                    res = res + QuerySumx(rs, m + 1, r, ql, qr);
                return res;
            }
        }
    
        ll QueryCnt(int o, int l, int r, int ql, int qr) {
            if(ql <= l && r <= qr) {
                return cnt[o];
            } else {
                int m = l + r >> 1;
                ll res = 0;
                if(ql <= m)
                    res = res + QueryCnt(ls, l, m, ql, qr);
                if(qr >= m + 1)
                    res = res + QueryCnt(rs, m + 1, r, ql, qr);
                return res;
            }
        }
    #undef ls
    #undef rs
    } Right;
    
    pii p[200005];
    int v[200005], vtop;
    
    void test_case() {
        int n;
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i)
            scanf("%d", &p[i].first);
        for(int i = 1; i <= n; ++i) {
            scanf("%d", &p[i].second);
            v[i] = p[i].second;
        }
        sort(p + 1, p + 1 + n);
        sort(v + 1, v + 1 + n);
        vtop = unique(v + 1, v + 1 + n) - (v + 1);
    
        ll ans = 0;
        Right.Build(1, 1, vtop);
        for(int i = 1; i <= n; ++i) {
            int pv = lower_bound(v + 1, v + 1 + vtop, p[i].second) - v;
            Right.Update1(1, 1, vtop, pv, p[i].first);
        }
        for(int i = 1; i <= n; ++i) {
            int pv = lower_bound(v + 1, v + 1 + vtop, p[i].second) - v;
            ans += Right.QuerySumx(1, 1, vtop, pv, vtop)
                   - 1ll * p[i].first * Right.QueryCnt(1, 1, vtop, pv, vtop);
            Right.Update2(1, 1, vtop, pv, p[i].first);
        }
        printf("%lld
    ", ans);
    }
    
    struct BinaryIndexTree {
        static const int MAXN = 200000;
        int n;
        ll bit1[MAXN + 5];
        int bit2[MAXN + 5];
    
        void Init(int _n) {
            n = _n;
            memset(bit1, 0, sizeof(bit1[0]) * (n + 1));
            memset(bit2, 0, sizeof(bit2[0]) * (n + 1));
        }
    
        void Add(int x, ll v1, int v2) {
            for(; x <= n; x += x & -x) {
                bit1[x] += v1;
                bit2[x] += v2;
            }
        }
    
        pair<ll, int> Sum(int x) {
            ll res1 = 0;
            int res2 = 0;
            for(; x; x -= x & -x) {
                res1 += bit1[x];
                res2 += bit2[x];
            }
            return {res1, res2};
        }
    } bit;
    
    pii p[200005];
    int v[200005], vtop;
    
    void test_case() {
        int n;
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i)
            scanf("%d", &p[i].first);
        for(int i = 1; i <= n; ++i) {
            scanf("%d", &p[i].second);
            v[i] = p[i].second;
        }
        sort(p + 1, p + 1 + n);
        sort(v + 1, v + 1 + n);
        vtop = unique(v + 1, v + 1 + n) - (v + 1);
    
        ll ans = 0;
        bit.Init(vtop);
        for(int i = 1; i <= n; ++i) {
            int pv = lower_bound(v + 1, v + 1 + vtop, p[i].second) - v;
            bit.Add(vtop - pv + 1, p[i].first, 1);
            p[i].second = pv;
        }
        for(int i = 1; i <= n; ++i) {
            int pv = p[i].second;
            pair<ll, int> res = bit.Sum(vtop - pv + 1);
            ans += res.first
                   - 1ll * p[i].first * res.second;
            bit.Add(vtop - pv + 1, -p[i].first, -1);
        }
        printf("%lld
    ", ans);
    }
    

    启发:只要排序关键字可以离线,就立刻离散化然后换成树状数组来做。

  • 相关阅读:
    第十二章类的无参方法
    第十三章人机猜拳
    第十一章类和对象
    面向对象七大原则。
    深入类的方法。
    使用集合组织相关数据。
    .NET框架
    C#数据类型
    错误。
    实现Windows的数据绑定
  • 原文地址:https://www.cnblogs.com/KisekiPurin2019/p/12423668.html
Copyright © 2020-2023  润新知