洛谷试炼场 4-13 主席树
题意:
给定一个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\n", 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\n", add(r, 2)-(add(r, 1)-h)/r); } } }
题意:
给出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\n", 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; } }
题意:
给出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\n", 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); } } }
题意:
给出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\n", pre); } }
题意:
给出一棵树,每个节点有自己的权值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\n", get_sum(x, y, c[x], 1)); } else { printf("%d\n", 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; } } } }