zoukankan      html  css  js  c++  java
  • 洛谷试炼场 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
  • 相关阅读:
    Django项目上线的准备工作
    Centos安装MySQL5.6并重置密码
    Centos7.4下安装Python3
    Django单表查询及其方法
    thinkphp 视图模型使用分析
    thinkphp 统计某个字段不重复数 总数
    表结构相同的表,且在同一数据库 数据复制
    crontab 定时任务 每过多少分钟执行
    js event事件
    shell 验证ip
  • 原文地址:https://www.cnblogs.com/Pneuis/p/9855945.html
Copyright © 2011-2022 走看看