洛谷试炼场 4-13 主席树

A.P2468 [SDOI2010]粟粟的书架

题意:

  给定一个R*C的矩阵。M次询问,每次给出一个子矩阵和一个数h。要求在子矩阵找出最少的数,使得这些数的和不小于h。求最少的数的个数,不存在则输出"Poor QLW"。

题解:

  观察一下这道题的数据范围:

  对于10%的数据,满足R, C≤10;

  对于20%的数据,满足R, C≤40;

  对于50%的数据,满足R, C≤200,M≤200,000;

  另有50%的数据,满足R=1,C≤500,000,M≤20,000;

  对于100%的数据,满足1≤Pi,j≤1,000,1≤Hi≤2,000,000,000

  可以发现只有R = 1时C才特别大。

  对于R = 1的情况可以用主席树维护区间和&区间点的个数来做。

  对于其他情况,由于P<=1000,所以可以二分二维前缀和来做。

#include <bits/stdc++.h>
using namespace std;
const int N = 5e5+10;
#define x1 xx
#define y1 yy
int r, c, m;
int a[N], root[N];
vector<int> g;
int tot;
int x1, y1, x2, y2, h;
int sum[N*20], num[N*20], L[N*20], R[N*20];
int k;
int s[205][205][1005];
int t[205][205][1005];
int getid(int x) {
    return lower_bound(g.begin(), g.end(), x)-g.begin()+1;
}
void update(int l, int r, int &x, int y, int pos) {
    sum[++tot] = sum[y]; num[tot] = num[y]; L[tot] = L[y]; R[tot] = R[y];
    sum[tot] += g[pos-1]; num[tot]++;
    x = tot;
    if(l == r) return ;
    int mid = l + r >> 1;
    if(pos <= mid) update(l, mid, L[x], L[y], pos);
    else update(mid+1, r, R[x], R[y], pos);
}
int query(int l, int r, int x, int y, int val) {
    if(l == r) {
        int cnt = num[y]-num[x];
        int v = (sum[y]-sum[x])/cnt;
        return (val+v-1)/v;
    }
    int mid = l + r >> 1;
    if(val > sum[R[y]]-sum[R[x]]) return num[R[y]]-num[R[x]]+query(l, mid, L[x], L[y], val-sum[R[y]]+sum[R[x]]);
    return query(mid+1, r, R[x], R[y], val);
}
int add(int p, int k) {
    if(k & 1) return s[x2][y2][p]-s[x2][y1-1][p]-s[x1-1][y2][p]+s[x1-1][y1-1][p];
    return t[x2][y2][p]-t[x2][y1-1][p]-t[x1-1][y2][p]+t[x1-1][y1-1][p];
}
int main() {
    scanf("%d%d%d", &r, &c, &m);
    if(r == 1) {
        for(int i = 1; i <= c; i++) {
            scanf("%d", &a[i]);
            g.push_back(a[i]);
        }
        sort(g.begin(), g.end());
        g.erase(unique(g.begin(), g.end()), g.end());
        int num = g.size();    
        for(int i = 1; i <= c; i++) update(1, num, root[i], root[i-1], getid(a[i]));
        while(m--) {
            scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &h);
            if(sum[root[y2]]-sum[root[y1-1]] < h) {
                puts("Poor QLW");
                continue;
            }
            printf("%d
", query(1, num, root[y1-1], root[y2], h));
        }
    }
    else {
        for(int i = 1; i <= r; i++) {
            for(int j = 1; j <= c; j++) {
                scanf("%d", &k);
                for(int p = 1000; p >= 1; p--) {
                    s[i][j][p] = s[i-1][j][p]+s[i][j-1][p]-s[i-1][j-1][p];
                    t[i][j][p] = t[i-1][j][p]+t[i][j-1][p]-t[i-1][j-1][p];
                    if(p <= k) {
                        s[i][j][p] += k;
                        t[i][j][p]++;
                    }
                }
            }
        }
        while(m--) {
            scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &h);
            if(add(1, 1) < h) {
                puts("Poor QLW");
                continue;
            }
            int l = 1, r = 1000;
            while(l <= r) {
                int mid = l + r >> 1;
                if(add(mid, 1) >= h) l = mid+1;
                else r = mid-1;
            }
            printf("%d
", add(r, 2)-(add(r, 1)-h)/r);
        }
    }
}
View Code

B.P3157 [CQOI2011]动态逆序对

题意:

  给出n个数的排列。m次操作,每次删除排列中的一个数。求每次删除前序列的逆序对数。

题解:

  维护每个数在原排列中的逆序对数,每次删掉一个点后把这个点的逆序对数减掉。

  但是这样会减掉多余的,即已经删掉的点。那么就要加上:已经删掉的位置在当前点前面且值大于它的点&已经删掉的位置在当前点后面且值小于它的点

  我的做法是在建立一棵主席树维护的树状数组(即带修改主席树),维护有多少个已经删掉的位置在当前点前面且值大于它的点

  再建立一棵普通的树状数组,维护有多少个已经删掉的且值大于当前点的点(注意没有位置)。

  我们已知已经删掉的点的个数

  再通过那棵主席树维护的树状数组,可以求出已经删掉的位置在当前点前面的点的个数

  那么已经删掉的位置在当前点前面的点的个数 - 已经删掉的位置在当前点前面且值大于它的点 = 已经删掉的位置在当前点前面且值小于它的点

    已经删掉的点的个数 - 已经删掉的且值大于当前点的点 - 已经删掉的位置在当前点前面且值小于它的点 = 已经删掉的位置在当前点后面且值小于它的点   

  当然简单点就直接建立两棵主席树维护的树状数组即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
typedef long long ll;
int n, m, k, tot;
int vis[N], root[N];
int sum[N*100], L[N*100], R[N*100];
int tre[N], v[N], tree[N];
void update(int l, int r, int &x, int y, int pos) {
    sum[++tot] = sum[y]+1; L[tot] = L[y]; R[tot] = R[y];
    x = tot;
    if(l == r) return ;
    int mid = l + r >> 1;
    if(pos <= mid) update(l, mid, L[x], L[y], pos);
    else update(mid+1, r, R[x], R[y], pos);
}
int query(int l, int r, int x, int pos) {
    if(pos == n+1) return 0;
    if(l == r) return sum[x];
    int mid = l + r >> 1;
    if(pos <= mid) return sum[R[x]]+query(l, mid, L[x], pos);
    else return query(mid+1, r, R[x], pos);
}
void add(int pos, int val) {
    while(pos <= n) {
        update(1, n, tre[pos], tre[pos], val);
        pos += pos&(-pos);
    }
}
void ins(int pos) {
    while(pos) {
        tree[pos]++;
        pos -= pos&(-pos);
    }
}
int get(int pos) {
    int res = 0;
    while(pos <= n) {
        res += tree[pos];
        pos += pos&(-pos);
    }
    return res;
}
int main() {
    scanf("%d%d", &n, &m);
    ll ans = 0;
    for(int i = 1; i <= n; i++) {
        scanf("%d", &k);
        vis[k] = i;
        v[i] = query(1, n, root[i-1], k);
        update(1, n, root[i], root[i-1], k);
        ans = ans+v[i];
        v[i] += k-i+v[i];
    }
    for(int j = 1; j <= m; j++) {
        printf("%lld
", ans);
        scanf("%d", &k);
        add(vis[k], k);
        ins(k);
        ans = ans-v[vis[k]];
        int cnt = 0, c = 0;
        for(int i = vis[k]; i; i -= i&(-i)) {
            cnt += query(1, n, tre[i], k+1);
            c += sum[tre[i]];
        }
        cnt += j-get(k+1)-c+cnt;
        ans = ans+cnt;
    }
    
}
View Code

C.P3302 [SDOI2013]森林

题意:

  给出N个点带有点权的森林,有两种操作并强制在线。第一种操作查询同一棵树上的x到y路径上权值第k小的点的权值;第二种操作将x和y连边(即合并两棵树)。

题解:

  对于每个节点,通过它的父节点建立它的主席树。还要维护每棵树的LCA。

  维护每棵树的节点个数(并查集),当合并两颗树时,将节点数小的树向大的合并。

  再暴力更新节点数小的树中每个点的主席树以及LCA(倍增数组)。

#include <bits/stdc++.h>
using namespace std;
const int N = 8e4+10;
int t, n, m, q;
int tot, num, cnt;
int u, v;
int x, y, k, la;
char s[2];
int dep[N];
int w[N], vis[N], root[N];
int head[N], nxt[N<<1], to[N<<1];
int f[N], sz[N];
int fa[N][20];
int sum[N*120], L[N*120], R[N*120];
vector<int> g;
int getid(int x) {
    return lower_bound(g.begin(), g.end(), x)-g.begin()+1;
}
int find(int x) {
    return x == f[x] ? x : f[x] = find(f[x]); 
}
void add_edge(int u, int v) {
    to[++cnt] = v; nxt[cnt] = head[u]; head[u] = cnt;
    to[++cnt] = u; nxt[cnt] = head[v]; head[v] = cnt;
}
void update(int l, int r, int &x, int y, int pos) {
    sum[++tot] = sum[y]+1; L[tot] = L[y]; R[tot] = R[y]; 
    x = tot;
    if(l == r) return ;
    int mid = l + r >> 1;
    if(pos <= mid) update(l, mid, L[x], L[y], pos);
    else update(mid+1, r, R[x], R[y], pos);
}
int query(int l, int r, int x1, int x2, int y1, int y2, int pos) {
    if(l == r) return l;
    int k = sum[L[x1]]+sum[L[x2]]-sum[L[y1]]-sum[L[y2]];
    int mid = l + r >> 1;
    if(pos > k) return query(mid+1, r, R[x1], R[x2], R[y1], R[y2], pos-k);
    return query(l, mid, L[x1], L[x2], L[y1], L[y2], pos);
}
void dfs(int u, int _f, int p, int d) {
    sz[p]++;
    f[u] = p;
    vis[u] = 1;
    dep[u] = d;
    fa[u][0] = _f;
    for(int i = 1; i <= 18; i++) fa[u][i] = fa[fa[u][i-1]][i-1];
    update(1, num, root[u], root[_f], getid(w[u]));
    for(int i = head[u]; ~i; i = nxt[i]) {
        if(to[i] == _f) continue;
        dfs(to[i], u, p, d+1);
    }
}
int lca(int u, int v) {
    if(dep[u] < dep[v]) swap(u, v);
    int k = dep[u]-dep[v];
    for(int i = 18; i >= 0; i--) {
        if((1<<i) & k) u = fa[u][i];
    }
    if(u == v) return u;
    for(int i = 18; i >= 0; i--) {
        if(fa[u][i] != fa[v][i]) {
            u = fa[u][i];
            v = fa[v][i];
        }
    }
    return fa[u][0];
}
int main() {
    memset(head, -1, sizeof(head));
    scanf("%d%d%d%d", &t, &n, &m, &q);
    for(int i = 1; i <= n; i++) {
        f[i] = i;
        scanf("%d", &w[i]);
        g.push_back(w[i]);
    }
    sort(g.begin(), g.end());
    g.erase(unique(g.begin(), g.end()), g.end());
    num = g.size();
    while(m--) {
        scanf("%d%d", &u, &v);
        add_edge(u, v);
    }
    for(int i = 1; i <= n; i++) {
        if(vis[i]) continue;
        dfs(i, 0, i, 0);
    }
    while(q--) {
        scanf("%s%d%d", s, &x, &y);
        x ^= la; y ^= la;
        if(s[0] == 'Q') {
            scanf("%d", &k);
            k ^= la;
            int p = lca(x, y);
            la = g[query(1, num, root[x], root[y], root[p], root[fa[p][0]], k)-1];
            printf("%d
", la);
        }
        else {
            add_edge(x, y);
            int px = find(x), py = find(y);
            if(sz[px] > sz[py]) {
                swap(x, y);
                swap(px, py);
            }
            fa[x][0] = y;
            f[px] = py;
            sz[px] = 0;
            dfs(x, y, py, dep[y]+1);
        }
    }
}
View Code

D.P3168 [CQOI2015]任务查询系统

题意:

  给出m个区间[Si,Ei],以及每个区间的权值Pi。求包含Xi的区间中,权值前Ki小的区间权值和。要求强制在线。

题解:

  直接在主席数上差分,Si处+1,Ei+1处-1即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
typedef long long ll;
int m, n, num;
int s, e, p;
ll pre;
int tot;
int x, A, B, C;
int root[N];
int w[N*40], L[N*40], R[N*40];
ll sum[N*40];
vector<int> g; 
int getid(int x) {
    return lower_bound(g.begin(), g.end(), x)-g.begin()+1;
}
struct node {
    int p, v;
    node() {}
    node(int a, int b) {
        p = a; v = b;
    }
    bool operator < (const node &a) {
        return p == a.p ? v < a.v : p < a.p;
    }
}a[N<<1];
void update(int l, int r, int &x, int y, int pos, int val) {
    sum[++tot] = sum[y]+g[pos-1]*val; w[tot] = w[y]+val; L[tot] = L[y]; R[tot] = R[y];
    x = tot;
    if(l == r) return ;
    int mid = l + r >> 1;
    if(pos <= mid) update(l, mid, L[x], L[y], pos, val);
    else update(mid+1, r, R[x], R[y], pos, val);
}
ll query(int l, int r, int x, int k) {
    if(l == r) return sum[x]/w[x]*k;
    int mid = l + r >> 1;
    if(w[L[x]] < k) return sum[L[x]]+query(mid+1, r, R[x], k-w[L[x]]);
    return query(l, mid, L[x], k);
}
int main() {
    scanf("%d%d", &m, &n);
    for(int i = 1; i <= m; i++) {
        scanf("%d%d%d", &s, &e, &p);
        g.push_back(p);
        a[i*2-1] = node(s, p);
        a[i*2] = node(e+1, -p);
    }
    sort(a+1, a+2*m+1);
    sort(g.begin(), g.end());
    g.erase(unique(g.begin(), g.end()), g.end());
    num = g.size();
    int cnt = 1;
    for(int i = 1; i <= n; i++) {
        root[i] = root[i-1];
        while(cnt <= 2*m && a[cnt].p == i) {
            update(1, num, root[i], root[i], getid(abs(a[cnt].v)), a[cnt].v/abs(a[cnt].v));
            cnt++;
        }
    }
    pre = 1;
    for(int i = 1; i <= n; i++) {
        scanf("%d%d%d%d", &x, &A, &B, &C);
        int k = (pre*A+B)%C+1;
        if(k >= w[root[x]]) pre = sum[root[x]];
        else pre = query(1, num, root[x], k);
        printf("%lld
", pre);
    }
} 
View Code

E.P3313 [SDOI2014]旅行

题意:

  给出一棵树,每个节点有自己的权值Wi以及类型Ci。给出四种操作:第一种是修改一个节点的类型;第二种是修改一个节点的权值;第三种是求点x到点y(x,y类型相同)路径上跟他们同类型的点的权值和;第四种是求点x到点y(x,y类型相同)路径上跟他们同类型的点的权值最大值。

题解:

  先跑一遍树链剖分。

  对于每一个类型Ci,动态开点建立一棵线段树。

  之后按操作修改即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int n, q;
int w[N], c[N];
int u, v, tot, cnt;
int x, y;
char s[5];
int head[N], nxt[N<<1], to[N<<1];
int fa[N], dep[N], sz[N], son[N], id[N], top[N], rk[N];
int root[N], mx[N*70], sum[N*70], L[N*70], R[N*70];
void push_up(int id, int l, int r) {
    sum[id] = sum[l]+sum[r];
    mx[id] = max(mx[l], mx[r]);
}
void update(int l, int r, int &x, int y, int pos, int val) {
    L[++tot] = L[y]; R[tot] = R[y]; 
    x = tot;
    if(l == r) {
        sum[x] = val;
        mx[x] = val;
        return ;
    }
    int mid = l + r >> 1;
    if(pos <= mid) update(l, mid, L[x], L[y], pos, val);
    else update(mid+1, r, R[x], R[y], pos, val);
    push_up(x, L[x], R[x]);
}
void dfs1(int u, int f, int d) {
    fa[u] = f;
    dep[u] = d;
    sz[u] = 1; 
    for(int i = head[u]; ~i; i = nxt[i]) {
        int v = to[i];
        if(v == f) continue;
        dfs1(v, u, d+1);   
        sz[u] += sz[v];   
        if(sz[v] > sz[son[u]])
            son[u] = v;   
    }
}
void dfs2(int u, int t) {
    top[u] = t;
    id[u] = ++cnt;   
    rk[cnt] = u;   
    update(1, n, root[c[u]], root[c[u]], id[u], w[u]);
    if(!son[u]) return ;
    dfs2(son[u], t);
    for(int i = head[u]; ~i; i = nxt[i]) {
        int v = to[i];
        if(v != son[u] && v != fa[u])
            dfs2(v, v);   
    }
}
int query(int x, int l, int r, int ql, int qr) {
    if(ql <= l && r <= qr) return sum[x];
    int res = 0;
    int mid = l + r >> 1;
    if(ql <= mid) res += query(L[x], l, mid, ql, qr);
    if(qr > mid) res += query(R[x], mid+1, r, ql, qr);
    return res;
}
int get_max(int x, int l, int r, int ql, int qr) {
    if(ql <= l && r <= qr) return mx[x];
    int res = 0;
    int mid = l + r >> 1;
    if(ql <= mid) res = max(res, get_max(L[x], l, mid, ql, qr));
    if(qr > mid) res = max(res, get_max(R[x], mid+1, r, ql, qr));
    return res;
}
int get_sum(int x, int y, int t, int k) {
    int res = 0, fx = top[x], fy = top[y];
    while(fx != fy) {
        if(dep[fx] < dep[fy]) {
            swap(fx, fy);
            swap(x, y);
        }
        if(k & 1) res += query(root[t], 1, n, id[fx], id[x]);
        else res = max(res, get_max(root[t], 1, n, id[fx], id[x]));
        x = fa[fx]; fx = top[x];
    }
    if(k & 1) res += query(root[t], 1, n, min(id[x], id[y]), max(id[x], id[y]));
    else res = max(res, get_max(root[t], 1, n, min(id[x], id[y]), max(id[x], id[y])));
    return res;
}
int main() {
    scanf("%d%d", &n, &q);
    memset(head, -1, sizeof(head));
    for(int i = 1; i <= n; i++) scanf("%d%d", &w[i], &c[i]);
    for(int i = 1; i < n; i++) {
        scanf("%d%d", &u, &v);
        to[++tot] = v; nxt[tot] = head[u]; head[u] = tot;
        to[++tot] = u; nxt[tot] = head[v]; head[v] = tot; 
    }
    tot = 0;
    dfs1(1, 0, 0);
    dfs2(1, 1);
    while(q--) {
        scanf("%s%d%d", s, &x, &y);
        if(s[0] == 'Q') {
            if(s[1] == 'S') {
                printf("%d
", get_sum(x, y, c[x], 1));
            }
            else {
                printf("%d
", get_sum(x, y, c[x], 2));
            }
        }
        else {
            if(s[1] == 'C') {
                update(1, n, root[c[x]], root[c[x]], id[x], 0);
                c[x] = y;
                update(1, n, root[c[x]], root[c[x]], id[x], w[x]);
            }
            else {
                update(1, n, root[c[x]], root[c[x]], id[x], y);
                w[x] = y;
            }
        }
    }
}
View Code
原文地址:https://www.cnblogs.com/Pneuis/p/9855945.html