标签: 权值线段树

[APIO2015]巴邻旁之桥 题解

[APIO2015]巴邻旁之桥 题解

题目地址:洛谷:【P3644】[APIO2015]八邻旁之桥 – 洛谷、BZOJ:Problem 4071. — [Apio2015]巴邻旁之桥

题目描述

一条东西走向的穆西河将巴邻旁市一分为二,分割成了区域 A 和区域 B。
每一块区域沿着河岸都建了恰好 1000000001 栋的建筑,每条岸边的建筑都从 0 编号到 1000000000。相邻的每对建筑相隔 1 个单位距离,河的宽度也是 1 个单位长度。区域 A 中的 i 号建筑物恰好与区域 B 中的 i 号建筑物隔河相对。
城市中有 N 个居民。第 i 个居民的房子在区域 Pi 的 Si 号建筑上,同时他的办公室坐落在 Qi 区域的 Ti 号建筑上。一个居民的房子和办公室可能分布在河的两岸,这样他就必须要搭乘船只才能从家中去往办公室,这种情况让很多人都觉得不方便。为了使居民们可以开车去工作,政府决定建造不超过 K 座横跨河流的大桥。
由于技术上的原因,每一座桥必须刚好连接河的两岸,桥梁必须严格垂直于河流,并且桥与桥之间不能相交。
当政府建造最多 K 座桥之后,设 Di 表示第 i 个居民此时开车从家里到办公室的最短距离。请帮助政府建造桥梁,使得 D1+D2+⋯+DN 最小。

输入输出格式

输入格式:
输入的第一行包含两个正整数 K 和 N,分别表示桥的上限数量和居民的数量。
接下来 N 行,每一行包含四个参数:Pi,Si,Qi 和 Ti,表示第 i 个居民的房子在区域 Pi 的 Si 号建筑上,且他的办公室位于 Qi 区域的 Ti 号建筑上。

输出格式:
输出仅为一行,包含一个整数,表示 D1+D2+⋯+DN 的最小值。

输入输出样例

输入样例#1:

1 5
B 0 A 4
B 1 B 3
A 5 B 7
B 2 A 6
B 1 A 7

输出样例#1:

24

输入样例#2:

2 5
B 0 A 4
B 1 B 3
A 5 B 7
B 2 A 6
B 1 A 7

输出样例#2:

22

说明

所有数据都保证:Pi 和 Qi 为字符 “A” 和 “B” 中的一个, 0≤Si,Ti≤1000000000,同一栋建筑内可能有超过 1 间房子或办公室(或二者的组合,即房子或办公室的数量同时大于等于 1)。
子任务 1 (8 分) K=1 1≤N≤1000
子任务 2 (14 分) K=1 1≤N≤100000
子任务 3 (9 分) K=2 1≤N≤100
子任务 4 (32 分) K=2 1≤N≤1000
子任务 5 (37 分) K=2 1≤N≤100000

题解

由于位置坐标比较大,先离散化这些位置。首先对于同侧的情况,建桥对其无影响,单独统计加入答案即可。
注意到K只有1和2两种取值,分开考虑。
K=1的时候,直接将桥建在所有家和办公室位置的中间(中位数)即可,这个位置的结果一定最优。
K=2的时候,考虑将这些线段分成两部分,以线段中点作为区分,中点较小的一部分走一座桥,另一部分走另一座桥。我们可以枚举排序后[1, i]内的线段属于一部分,其他属于另一部分,分别求中点计算答案。
计算中点可以考虑使用权值线段树来处理。上述算法的复杂度是O(N \log N)
但是非常可惜,直接做会爆long long,这里用一点小trick可以避免答案过大,详情参见代码。

代码

// Code by KSkun, 2018/5
#include <cstdio>
#include <cctype>

#include <algorithm>
#include <vector>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF 
        : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    register char c = fgc();
    while(!isdigit(c)) {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(isdigit(c)) {
        res = (res << 1) + (res << 3) + c - '0';
        c = fgc();
    }
    return res * neg;
}

inline bool isAB(char c) {
    return c == 'A' || c == 'B';
}

inline char readAB() {
    char c;
    while(!isAB(c = fgc()));
    return c;
}

const int MAXN = 200005;

std::vector<LL> tmp;

struct SegmentTree {
#define lch o << 1
#define rch o << 1 | 1
#define mid ((l + r) >> 1)
    LL val[MAXN << 2], sum[MAXN << 2];
    inline void insert(int o, int l, int r, int x) {
        val[o]++; sum[o] += tmp[x];
        if(l == r) return;
        if(x <= mid) insert(lch, l, mid, x);
        else insert(rch, mid + 1, r, x);
    }
    inline void erase(int o, int l, int r, int x) {
        val[o]--; sum[o] -= tmp[x];
        if(l == r) return;
        if(x <= mid) erase(lch, l, mid, x);
        else erase(rch, mid + 1, r, x);
    }
    inline int queryrk(int o, int l, int r, int rk) {
        if(l == r) return l;
        int lsiz = val[lch];
        if(rk <= lsiz) return queryrk(lch, l, mid, rk);
        else return queryrk(rch, mid + 1, r, rk - lsiz);
    }
    inline LL querysum(int o, int l, int r, int ll, int rr) {
        if(l >= ll && r <= rr) return sum[o];
        LL res = 0;
        if(ll <= mid) res += querysum(lch, l, mid, ll, rr);
        if(rr > mid) res += querysum(rch, mid + 1, r, ll, rr);
        return res;
    }
    inline LL querysiz(int o, int l, int r, int ll, int rr) {
        if(l >= ll && r <= rr) return val[o];
        LL res = 0;
        if(ll <= mid) res += querysiz(lch, l, mid, ll, rr);
        if(rr > mid) res += querysiz(rch, mid + 1, r, ll, rr);
        return res;
    }
} tr1, tr2;

int k, n, N;
LL ans;

struct Node {
    LL s, t;
    inline bool operator<(const Node &rhs) const {
        return s + t < rhs.s + rhs.t;
    }
};

std::vector<Node> info;

inline int main1() {
    LL sum1 = 0, sum2 = 0;
    for(int i = 1; i <= N / 2; i++) {
        sum1 += tmp[i];
    }
    for(int i = N / 2 + 1; i <= N; i++) {
        sum2 += tmp[i];
    }
    printf("%lld", ans + sum2 - sum1);
    return 0;
}

inline int main2() {
    if(!info.size()) {
        printf("%lld", ans); return 0;
    }
    std::sort(info.begin(), info.end());
    for(int i = 0; i < info.size(); i++) {
        info[i].s = std::lower_bound(tmp.begin(), tmp.end(), info[i].s) - tmp.begin();
        info[i].t = std::lower_bound(tmp.begin(), tmp.end(), info[i].t) - tmp.begin();
    }
    for(int i = 0; i < info.size(); i++) {
        tr1.insert(1, 1, N, info[i].s); tr1.insert(1, 1, N, info[i].t);
    }
    LL mn = 1e18;
    for(int i = 0; i < info.size(); i++) {
        tr1.erase(1, 1, N, info[i].s); tr1.erase(1, 1, N, info[i].t);
        tr2.insert(1, 1, N, info[i].s); tr2.insert(1, 1, N, info[i].t);
        LL m1 = tr1.queryrk(1, 1, N, tr1.val[1] / 2), 
            m2 = tr2.queryrk(1, 1, N, tr2.val[1] / 2),
            d1 = tmp[m1] * tr1.querysiz(1, 1, N, 1, m1) - tr1.querysum(1, 1, N, 1, m1) 
                + tr1.querysum(1, 1, N, m1, N) - tmp[m1] * tr1.querysiz(1, 1, N, m1, N),
            d2 = tmp[m2] * tr2.querysiz(1, 1, N, 1, m2) - tr2.querysum(1, 1, N, 1, m2) 
                + tr2.querysum(1, 1, N, m2, N) - tmp[m2] * tr2.querysiz(1, 1, N, m2, N);
        mn = std::min(mn, d1 + d2);
    }
    printf("%lld", ans + mn);
    return 0;
}

LL si, ti;
char pi, qi;

int main() {
    k = readint(); n = readint();
    for(int i = 1; i <= n; i++) {
        pi = readAB(); si = readint(); qi = readAB(); ti = readint();
        if(si > ti) {
            std::swap(pi, qi); std::swap(si, ti);
        }
        if(pi == qi) ans += ti - si;
        else ans++, info.push_back(Node {si, ti});
    }
    tmp.push_back(-1);
    for(int i = 0; i < info.size(); i++) {
        tmp.push_back(info[i].s);
        tmp.push_back(info[i].t);
    }
    std::sort(tmp.begin(), tmp.end());
    N = tmp.size() - 1;
    if(k == 1) return main1();
    tmp.erase(std::unique(tmp.begin(), tmp.end()), tmp.end());
    N = tmp.size() - 1;
    return main2();
}
[CC-GERALD07]Chef and Graph Queries 题解

[CC-GERALD07]Chef and Graph Queries 题解

题目地址:CodeChef:Chef and Graph Queries | CodeChef

题目描述

大厨有一个无向图G。顶点从1到N标号,边从1到M标号。
大厨有Q对询问Li,Ri(1≤Li≤Ri≤M)。对于每对询问,大厨想知道当仅保留编号X满足Li≤X≤Ri所在的边时,图G中有多少连通块。
请帮助大厨回答这些询问。

输入输出格式

输入格式:
输入数据的第一行包含一个整数T,表示数据组数。对于每组数据,第一行包含三个整数N,M,Q。接下来的M行,每行包含两个整数Vi,Ui,依次表示一条无向边。接下来的Q行,每行包含两个整数Li,Ri,依次表示一组询问。

输出格式:
对于每组询问,输出对应的连通块数。

输入输出样例

输入样例#1:

2
3 5 4
1 3
1 2
2 1
3 2
2 2
2 3
1 5
5 5
1 2
1 1 1
1 1
1 1

输出样例#1:

2
1
3
1
1

说明

  • 1≤T≤1000
  • 1≤N, M, Q≤200000
  • 1≤Ui, Vi≤N
  • 1≤Li≤Ri≤M
  • 数据保证所有N的和不超过200000。所有M的和不超过200000。所有Q的和不超过200000。
  • 注意数据可能包含自环和重边!

题解

我们考虑一条边会对连通块产生什么影响,加一条边会合并某两个连通块,使得答案减少1,当然这仅当加这条边不会成环以及没有重边和自环的时候才会产生影响。因此我们考虑维护一个生成树,使得每加一条边,如果成环,删去该环中编号最小的边。该生成树上编号为[Li, Ri]的边有多少条,答案就是n-几。我们可以在逐条加边的过程中动态维护生成树以及生成树上的边编号计数。动态维护生成树可以用LCT做,而边编号计数则使用权值线段树。
这道题目的强制在线升级版可以看[BZOJ3514]Codechef MARCH14 GERALD07加强版 题解 | KSkun’s Blog
LCT和线段树都是常数大的东西,还好跑过了。

代码

// Code by KSkun, 2018/3
#include <cstdio>
#include <cstring>

#include <algorithm>

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}

inline int readint() {
    register int res = 0, neg = 1;
    char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 400005, INF = 1e9;

struct LCTNode {
    int ch[2], fa, val, mn;
    bool rev;
} lct[MAXN];

inline bool isleft(int p) {
    return lct[lct[p].fa].ch[0] == p;
}

inline bool isroot(int p) {
    register int fa = lct[p].fa;
    return lct[fa].ch[0] != p && lct[fa].ch[1] != p;
}

inline void update(int p) {
    register int ls = lct[p].ch[0], rs = lct[p].ch[1];
    lct[p].mn = p;
    if(lct[lct[ls].mn].val < lct[lct[p].mn].val) lct[p].mn = lct[ls].mn;
    if(lct[lct[rs].mn].val < lct[lct[p].mn].val) lct[p].mn = lct[rs].mn;
}

inline void reverse(int p) {
    std::swap(lct[p].ch[0], lct[p].ch[1]);
    lct[p].rev ^= 1;
}

inline void pushdown(int p) {
    register int ls = lct[p].ch[0], rs = lct[p].ch[1];
    if(lct[p].rev) {
        if(ls) reverse(ls);
        if(rs) reverse(rs);
        lct[p].rev ^= 1;
    }
}

int sta[MAXN], stop;

inline void pushto(int p) {
    stop = 0;
    while(!isroot(p)) {
        sta[stop++] = p;
        p = lct[p].fa;
    }
    pushdown(p);
    while(stop) {
        pushdown(sta[--stop]);
    }
}

inline void rotate(int p) {
    register bool t = !isleft(p); register int fa = lct[p].fa, ffa = lct[fa].fa;
    lct[p].fa = ffa; if(!isroot(fa)) lct[ffa].ch[!isleft(fa)] = p;
    lct[fa].ch[t] = lct[p].ch[!t]; lct[lct[fa].ch[t]].fa = fa;
    lct[p].ch[!t] = fa; lct[fa].fa = p;
    update(fa);
}

inline void splay(int p) {
    pushto(p);
    for(register int fa = lct[p].fa; !isroot(p); rotate(p), fa = lct[p].fa) {
        if(!isroot(fa)) rotate(isleft(fa) == isleft(p) ? fa : p);
    }
    update(p);
}

inline void access(int p) {
    for(register int q = 0; p; q = p, p = lct[p].fa) {
        splay(p);
        lct[p].ch[1] = q;
        update(p);
    }
}

inline void makert(int p) {
    access(p);
    splay(p);
    reverse(p);
}

inline int findrt(int p) {
    access(p);
    splay(p);
    while(lct[p].ch[0]) p = lct[p].ch[0];
    return p;
}

inline void split(int u, int v) {
    makert(u);
    access(v);
    splay(v);
}

inline void link(int u, int v) {
    split(u, v);
    lct[u].fa = v;
}

inline void cut(int u, int v) {
    split(u, v);
    if(lct[v].ch[0] != u || lct[lct[v].ch[0]].ch[1]) return;
    lct[u].fa = lct[v].ch[0] = 0;
}

inline int query(int u, int v) {
    split(u, v);
    return lct[v].mn;
}

int sgt[MAXN << 2];

inline void insert(int o, int l, int r, int x, int v) {
    if(l == r) {
        sgt[o] += v;
        return;
    }
    register int mid = (l + r) >> 1;
    if(x <= mid) insert(o << 1, l, mid, x, v);
    else insert((o << 1) | 1, mid + 1, r, x, v);
    sgt[o] = sgt[o << 1] + sgt[(o << 1) | 1];
}

inline int query(int o, int l, int r, int ll, int rr) {
    if(l >= ll && r <= rr) return sgt[o];
    register int mid = (l + r) >> 1, res = 0;
    if(ll <= mid) res += query(o << 1, l, mid, ll, rr);
    if(rr > mid) res += query((o << 1) | 1, mid + 1, r, ll, rr);
    return res;
}

int T, n, m, q, ut, vt, lst[MAXN], u[MAXN], v[MAXN], ans[MAXN];

struct Edge {
    int u, v;
} edge[MAXN];

struct Query {
    int l, r, id;
    inline bool operator<(const Query &rhs) const {
        return r < rhs.r;
    }
} quer[MAXN];

int main() {
    T = readint();
    while(T--) {
        n = readint(); m = readint(); q = readint();
        // init
        for(int i = 0; i <= n + m; i++) {
            lct[i].ch[0] = lct[i].ch[1] = lct[i].fa = lct[i].mn = lct[i].rev = 0;
            lct[i].val = INF;
        }
        memset(sgt, 0, sizeof(sgt));
        // read
        for(int i = 1; i <= m; i++) {
            edge[i].u = readint(); edge[i].v = readint(); 
        }
        for(int i = 1; i <= q; i++) {
            quer[i].l = readint(); quer[i].r = readint(); quer[i].id = i;
        }
        std::sort(quer + 1, quer + q + 1);
        // work
        int tot = n, ptr = 1;
        for(int i = 1; i <= m; i++) {
            int u = edge[i].u, v = edge[i].v;
            if(u != v && findrt(u) == findrt(v)) {
                int t = query(u, v), tv = lct[t].val;
                cut(edge[tv].u, t);
                cut(edge[tv].v, t);
                insert(1, 1, m, tv, -1);
            }
            if(u != v) {
                tot++;
                lct[tot].mn = tot;
                lct[tot].val = i;
                link(u, tot);
                link(v, tot);
                insert(1, 1, m, i, 1);
            }
            while(ptr <= q && quer[ptr].r <= i) {
                if(quer[ptr].r == i) {
                    ans[quer[ptr].id] = n - query(1, 1, m, quer[ptr].l, quer[ptr].r);
                }
                ptr++;
            }
        }
        for(int i = 1; i <= q; i++) {
            printf("%d\n", ans[i]);
        }
    }
    return 0;
}
可持久化线段树(含主席树)原理与实现

可持久化线段树(含主席树)原理与实现

概述

可持久化线段树是一类线段树的实现方式,用于保存线段树的历史版本。本文后半部分所介绍的一种用法是主席树这一类可持久化线段树。主席树是以下标为时间加入元素的一类可持久化线段树,常见的用法是静态查询区间第k大值。下面我们介绍它的原理和实现。

原理

什么叫“可持久”?

可持久的意思是保存历史版本,可以查询到历史操作的结果。比如,对于一个序列进行更改,你可以选择每一次更改前将上次更改后的序列复制一份,再在新序列中更改,以保留这个序列的所有历史版本。同样地,你也可以建一堆线段树来保留线段树的历史版本。但是,这样做的空间是O(Q * 4N)的,显然很难接受,因此我们要使用可持久化线段树这种实现方法。

结构

一棵可持久化线段树的结构如图所示。
180226a 1 - 可持久化线段树(含主席树)原理与实现
如图,黑色的为最初版本的线段树,蓝色的为版本2。

修改

如示例图所示,线段树的修改应该是修改一条链,也就是两个相邻版本的的线段树只会有一条链有差别。那我们考虑动态开点,将后面版本的线段树那些没有更改的儿子指向前一个版本的线段树的节点,这样每次只会新建一条链的节点,空间降低为O(4N + Q \log N),是可以接受的了。

查询

每次修改后存储这一次修改的新线段树的根,然后按照根下去正常查询即可。

实现

修改

每次修改前令本次的根为上一次的根,再执行下面的操作。

inline void insert(int &o, int l, int r, int x) {
    tree[++tot] = tree[o];
    o = tot;
    tree[o].val++;
    if(l == r) return;
    int mid = (l + r) >> 1;
    if(x <= mid) insert(tree[o].lch, l, mid, x);
    else insert(tree[o].rch, mid + 1, r, x);
}

查询

常规操作,不提供实现细节。

主席树(查询区间k小值)

原理

我们考虑使用权值线段树,对原序列一个一个插入可持久化线段树。这样,线段树对应区间就是前若干数中该区间内数的个数。对于查询,我们考虑第r和第l-1棵线段树,对应区间值的差就是询问区间中的答案。所以我们的查询操作可以同时沿着这两棵线段树走。

实现

以下是查询操作的实现。

inline int query(int o1, int o2, int l, int r, int k) {
    if(l == r) return l;
    int mid = (l + r) >> 1;
    int num = tree[tree[o2].lch].val - tree[tree[o1].lch].val;
    if(k <= num) return query(tree[o1].lch, tree[o2].lch, l, mid, k);
    else return query(tree[o1].rch, tree[o2].rch, mid + 1, r, k - num);
}

代码

本代码可以通过【P3834】【模板】可持久化线段树 1(主席树) – 洛谷一题。

// Code by KSkun, 2018/2
#include <cstdio>
#include <algorithm>

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}

inline int readint() {
    register int res = 0, neg = 1;
    char c = fgc(); 
    while (c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while (c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 200005;

struct Node {
    int lch, rch, val;
} tree[MAXN << 5];
int tot = 0, rt[MAXN];

inline void insert(int &o, int l, int r, int x) {
    tree[++tot] = tree[o];
    o = tot;
    tree[o].val++;
    if(l == r) return;
    int mid = (l + r) >> 1;
    if(x <= mid) insert(tree[o].lch, l, mid, x);
    else insert(tree[o].rch, mid + 1, r, x);
}

inline int query(int o1, int o2, int l, int r, int k) {
    if(l == r) return l;
    int mid = (l + r) >> 1;
    int num = tree[tree[o2].lch].val - tree[tree[o1].lch].val;
    if(k <= num) return query(tree[o1].lch, tree[o2].lch, l, mid, k);
    else return query(tree[o1].rch, tree[o2].rch, mid + 1, r, k - num);
}

int n, m, a[MAXN], tmp[MAXN],ttot, l, r, k;

int main() {
    n = readint();
    m = readint();
    for(int i = 1; i <= n; i++) {
        a[i] = tmp[i] = readint();
    }
    std::sort(tmp + 1, tmp + n + 1);
    ttot = std::unique(tmp + 1, tmp + n + 1) - tmp - 1;
    for(int i = 1; i <= n; i++) {
        rt[i] = rt[i - 1];
        insert(rt[i], 1, ttot, std::lower_bound(tmp + 1, tmp + ttot + 1, a[i]) - tmp);
    } 
    while(m--) {
        l = readint();
        r = readint();
        k = readint();
        printf("%d\n", tmp[query(rt[l - 1], rt[r], 1, ttot, k)]);
    }
    return 0;
}