线段树合并
做了多少忘了多少,翻开提交记录全是抄的题解,连变量名是啥意思都想不起来了。
A. Promotion Counting
“高二上几调的solution”中让我去启发式合并,于是就想到了复习一下。
记录个数,如果不用线段树合并的话,就需要从每个节点往下找它的孩子,每换一个点都需要清空一次,线段树合并的作用就是让已经找到的子节点的个数被利用。由于满足条件的标准在变化,不能用比较直接的方法来统计个数,比如dfs子树的大小。
对每一个数值,都单独的开一棵权值线段树。点的关系决定了这些线段树被合并到一起的方式。
and权值线段树:桶我们经常使用,例如基数排序是用cnt[]数组记录每个数出现的次数。权值线段树就是用线段树维护一个桶,它可以O(log2v)(v是值域)查询某个范围内的数出现的总次数,需要按值域开空间,当值域过大时需要离散化或动态开点。每个叶子节点的值代表这个值的出现次数,非叶子结点的值代表它管辖的值域内所有值出现的次数的和。
复习一下发现这个题还挺板子的……
离散化或动态开点用一个就够了,下面的code选择了离散化。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1e5 + 10; const int Inf = 0xfffffff; #define re register #define lson(x) tree[x].lson #define rson(x) tree[x].rson int n, c[maxn], vl[maxn], cnt, fa[maxn], rt[maxn], ans[maxn], tot; vector<int> q[maxn]; struct node { int lson, rson, dt, tg; }tree[maxn*40]; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } //dt是个数,tg是位置,也就是加的是谁的权值 inline void pushup(int rt) { tree[rt].dt = tree[lson(rt)].dt + tree[rson(rt)].dt; } inline int update(int rt, int l, int r, int pos) { if(!rt) rt = ++tot; if(l == r) { tree[rt].dt++; tree[rt].tg = pos; //printf("tree[%d].dt == %d tree[%d].tg == %d\n", rt, tree[rt].dt, rt, tree[rt].tg); return rt; } int mid = (l + r) >> 1; if(pos <= mid) lson(rt) = update(lson(rt), l, mid, pos); else rson(rt) = update(rson(rt), mid+1, r, pos); pushup(rt); return rt; } inline int segmerge(int ra, int rb, int l, int r) { if(!ra) return rb; if(!rb) return ra; if(l == r) { tree[ra].dt += tree[rb].dt; //printf("%d %d\n", tree[ra].tg, tree[rb].tg); tree[ra].tg = tree[rb].tg; } int mid = (l + r) >> 1; lson(ra) = segmerge(lson(ra), lson(rb), l, mid); rson(ra) = segmerge(rson(ra), rson(rb), mid+1, r); //注意第二行是rson(ra) 不能写成rson(rb) pushup(ra); return ra; } inline int query(int rt, int l, int r, int L, int R) { if(L <= l && r <= R) { return tree[rt].dt; } int mid = (l + r) >> 1; int val = 0; if(L <= mid) val += query(tree[rt].lson, l, mid, L, R); if(R > mid) val += query(tree[rt].rson, mid+1, r, L, R); return val; } void dfs(int x) { int sz = q[x].size(); for(int i=0; i<sz; i++) { int to = q[x][i]; if(to == fa[x]) continue; dfs(to); rt[x] = segmerge(rt[x], rt[to], 1, cnt);//把子树合并到根 //printf("rt[%d] == %d\n", x, rt[x]); } //printf("--rt[%d] == %d\n", x, rt[x]); ans[x] = query(rt[x], 1, cnt, vl[x]+1, cnt); //printf("ans[%d] == %d\n", x, ans[x]); } int main() { n = read(); for(int i=1; i<=n; i++) { vl[i] = read(); c[i] = vl[i]; } sort(c+1, c+1+n); cnt = unique(c+1, c+1+n) - c - 1; for(int i=1; i<=n; i++) { vl[i] = lower_bound(c+1, c+1+n, vl[i]) - c; rt[i] = update(rt[i], 1, cnt, vl[i]);//每一个数值建一棵权值树 } for(int i=2; i<=n; i++) { int x = read(); fa[i] = x; q[x].push_back(i); q[i].push_back(x); } dfs(1); for(int i=1; i<=n; i++) { printf("%d\n", ans[i]); } return 0; }
B. bzoj4399: 魔法少女LJJ
唯一的印象是,当初这道题比较难调,抄题解很容易串行。
1.新建一个节点,权值为x。 2.连接两个节点。 3.将一个节点a所属于的联通快内权值小于x的所有节点权值变成x。 4.将一个节点a所属于的联通快内权值大于x的所有节点权值变成x。 5.询问一个节点a所属于的联通块内的第k小的权值是多少。 6.询问一个节点a所属联通快内所有节点权值之积与另一个节点b所属联通快内所有节点权值之积的大小。 7.询问a所在联通快内节点的数量
哦,第二个印象也回忆起来了:这玩意儿和线段树合并有什么关系?
第三个印象:什么鬼?这也能离线?疯了吧……不过后来也确实不是我想象中的那种疯狂离线,只是记录了一下值域,先把每个可能作为点权的数值放到数组里,问题的顺序当然不能变。
取log是因为6要求比较权值之积的大小,log是单调函数,不影响相对值,还可以把乘法转化成加法,既避免了超数据范围,又使积的记录有了传递性。至于连通块,就用并查集来维护,线段树合并就是在这里用到了,两个合并同步进行,注意根和孩子要一一对应,尽管两个单独来看方向不重要,但合起来就不一样了,线段树类型还是权值线段树,用并查集如果有断边就麻烦了,好在数据范围是op<=7,后面的操作不用管它。懒惰标记和清空函数是一套操作,这是把区间修改拆成了先删除再添加两个操作。查找第k小的权值的操作和平衡树的原理简直一样,我说当时学平衡树的那部分操作怎么有种似曾相识的感觉……
and离散化和动态开点也不是不能同时用。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 400005; const int Inf = 0xfffffff; #define re register int m, n, d[maxn], tot, cnt; double val[maxn*18]; int rt[maxn], fa[maxn]; bool mark[maxn*18]; int lc[maxn*18], rc[maxn*18], Size[maxn*18]; struct node2 { int op, x, y; }q[maxn]; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } int find(int x) { if(fa[x] == x) return x; return fa[x] = find(fa[x]); } int idx(int x) { return lower_bound(d+1, d+n+1, x) - d; } void pushup(int rt) { val[rt] = val[lc[rt]] + val[rc[rt]]; Size[rt] = Size[lc[rt]] + Size[rc[rt]]; } void pushnow(int rt) { Size[rt] = val[rt] = 0; mark[rt] = 1; } void pushdown(int rt) { if(!mark[rt]) return; pushnow(lc[rt]), pushnow(rc[rt]); mark[rt] = 0; } void insert(int &rt, int l, int r, int pos, int num1, double num2) { int mid = (l + r) >> 1; if(!rt) rt = ++tot; pushdown(rt); if(l == r) { Size[rt] = num1, val[rt] = num2; return; } if(pos <= mid) insert(lc[rt], l, mid, pos, num1, num2); else insert(rc[rt], mid+1, r, pos, num1, num2); pushup(rt); } int Merge2(int x, int y, int l, int r) { if(!x || !y) return x+y; val[x] += val[y], Size[x] += Size[y]; int mid = (l + r) >> 1; if(l == r) return x; pushdown(x), pushdown(y); lc[x] = Merge2(lc[x], lc[y], l, mid); rc[x] = Merge2(rc[x], rc[y], mid+1, r); return x; } void Merge1(int x, int y) { x = find(x), y = find(y); if(x != y) fa[y] = x, rt[x] = Merge2(rt[x], rt[y], 1, n); } int query(int rt, int l, int r, int x, int y) { if(!rt) return 0; if(x <= l && r <= y) return Size[rt]; int mid = (l + r) >> 1; pushdown(rt); if(y <= mid) return query(lc[rt], l, mid, x, y); if(x > mid) return query(rc[rt], mid+1, r, x, y); return query(lc[rt], l, mid, x, y) + query(rc[rt], mid+1, r, x, y); } void modify(int rt, int l, int r, int L, int R) { if(!rt) return; if(L <= l && r <= R) { return pushnow(rt); } int mid = (l + r) >> 1; pushdown(rt); if(L <= mid) modify(lc[rt], l, mid, L, R); if(R > mid) modify(rc[rt], mid+1, r, L, R); pushup(rt); } int ask(int rt, int l, int r, int x) { if(l == r) return d[l]; int mid = (l + r) >> 1; pushdown(rt); if(Size[lc[rt]] >= x) return ask(lc[rt], l, mid, x); return ask(rc[rt], mid+1, r, x-Size[lc[rt]]); } int main() { int x, y; m = read(); for(int i=1; i<=m; i++) { q[i].op = read(); q[i].x = read(); if(q[i].op != 1 && q[i].op != 7) q[i].y = read(); if(q[i].op == 3 || q[i].op == 4) d[++n] = q[i].y; if(q[i].op == 1) d[++n] = q[i].x; } sort(d+1, d+1+n); n = unique(d+1, d+1+n) - (d+1); for(int i=1; i<=m; i++) { if(q[i].op == 1) { insert(rt[++cnt], 1, n, idx(q[i].x), 1, log2(q[i].x)); fa[cnt] = cnt; } else if(q[i].op == 2) Merge1(q[i].x, q[i].y); else if(q[i].op == 3) { x = find(q[i].x), y = idx(q[i].y); int sum = query(rt[x], 1, n, 1, y); modify(rt[x], 1, n, 1, y); insert(rt[x], 1, n, y, sum, sum*log2(q[i].y)); } else if(q[i].op == 4) { x = find(q[i].x), y = idx(q[i].y); int sum = query(rt[x], 1, n, y, n); modify(rt[x], 1, n, y, n); insert(rt[x], 1, n, y, sum, sum*log2(q[i].y)); } else if(q[i].op == 5) printf("%d\n", ask(rt[find(q[i].x)], 1, n, q[i].y)); else if(q[i].op == 6) printf("%d\n", (val[rt[find(q[i].x)]] > val[rt[find(q[i].y)]]) ? 1 : 0); else printf("%d\n", Size[rt[find(q[i].x)]]); } return 0; }
D. 雨天的尾巴
说起树上差分,我忽然想到了一个差分的题,和线段树没什么关系,打算把它放在这里收藏一下,我看到那个题时的第一印象好像使用分块做……奇奇妙妙。
>>插入:同是D题好巧合……
D. 小 X 与煎饼达人
区间的正反改变可以用差分数组,求和就成了当前位置的状态。脑洞说我又想到了树状数组的区间修改单点查询,啊我又联想到了一个题——
——啊不过还是先把这个题的code放在这:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1e6 + 2; const int Inf = 0xfffffff; #define re register int n, m; int c[maxn], ans; ll sum[maxn]; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } int main() { freopen("flip.in", "r", stdin); freopen("flip.out", "w", stdout); n = read(); m = read(); for(int i=1; i<=m; i++) { int x = read(), y = read(); c[x]++; c[y+1]--; } for(int i=1; i<=n; i++) { sum[i] = sum[i-1]; sum[i] += c[i]; if(sum[i] & 1) ans++; } printf("%d", ans); return 0; }
B. 冒泡排序(2022高考集训2)
代码里的注释好像够了,那我就不再写一遍了。
/* 专业抄题解100年 你看到我自嘲地苦笑了吗 */ #include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 2e5 + 3; const int mod = 1e9 + 7; const int INF = 0x7fffffff; int n, tree1[5005], tree2[5005], num[5005], st1[5005], st2[5005]; int dp[5005][5005]; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } void up(int a, int b, int *s) { while(a <= n) { s[a] += b; a += a & (-a); } return; } inline int down(int &a, const int &s) { if(s == 1) { return tree1[a] + st1[a-(a&(-a))]; } else { return tree2[a] + st2[a-(a&(-a))]; } } /* 我就不信我看不出来这到底是什么意思!! */ int main() { freopen("mp.in", "r", stdin); freopen("mp.out", "w", stdout); int ret = 0; n = read(); for(int i=1; i<=n; i++) { num[i] = read() + 1; if(num[i] == i) { printf("0"); exit(0); } else if(num[i] > i) { up(i, 1, tree1);//在第一个树状数组上单点修改 //本来应该是区间修改,树状数组表示优先级,而优先级一定是连续的 up(num[i]-1, -1, tree1);//大数往后换 //printf("1: up(%d, 1)\n", i); //printf("1: up(%d, -1)\n", num[i]-1); } else { up(num[i], 1, tree2); up(i-1, -1, tree2);//小数往前换 //printf("2: up(%d, 1)\n", num[i]); //printf("2: up(%d, -1)\n", i-1); } } for(int i=1; i<n; i++) { st1[i] = down(i, 1); st2[i] = down(i, 2); //printf("st1[%d] == %d\n", i, st1[i]); //printf("st2[%d] == %d\n", i, st2[i]); //为什么需要分别对两棵树查询前缀和 //为什么要算这么多前缀和 //好吧我才知道这是差分数组。。 //所以st1就表示当前点是不是对于后面的点优先(先和后面换在和前面换) //st2表示是不是对于前面的点优先 if(st1[i] && st2[i])//那么优先级出现了矛盾 { printf("0"); exit(0); } } st1[0] = st2[0] = st1[n] = st2[n] = 0; dp[1][1] = 1; //f[a][b]第a个点在第b个位置被交换 for(int i=2,j=1; i<n; j=i,i++)//int j=i-1 { if(st1[j]) { for(int k=2; k<=i; k++)//新的点如果排在k,那么i一定要在j之前的任意位置被交换 { dp[i][k] = dp[i][k-1] + dp[j][k-1]; if(dp[i][k] >= mod) { dp[i][k] -= mod; } } } else if(st2[j]) { for(int k=j; k; k--) { dp[i][k] = dp[i][k+1] + dp[j][k]; if(dp[i][k] >= mod) { dp[i][k] -= mod; } } } else//随便的关系 { for(int k=1; k<=j; k++) { dp[i][1] += dp[j][k]; if(dp[i][1] >= mod) { dp[i][1] -= mod; } } for(int k=2; k<=i; k++) { dp[i][k] = dp[i][k-1]; } } } for(int i=1; i<n; i++) { ret += dp[n-1][i]; if(ret >= mod) { ret -= mod; } } printf("%d", ret); return 0; }
跑偏的思路还是要跑回来:不过,休息一下,未完待续?算了,直接到蓝书382页附近去找吧,懒了。

浙公网安备 33010602011771号