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);
}

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

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