1.并查集
2.线段树 扫描线 主席树等
3.trie树 可持久化trie树
4.散列表
5.字符串哈希
6.模拟堆
7.莫队
8.双向链表
9.树链剖分
10.左偏树(可合并堆)
11.克鲁斯卡尔重构树
关于并查集:
一定是他的集合与集合里面的最顶层的父亲的关系
若是权值并查集 一定是父亲的值相加减 因为成员的值不一定准确
acwing 239
题目大意 给你
一个n 假设有一个01序列
给你一个m 给你m个条件 问你第几次条件的时候是矛盾的
string a
m的 格式是 给你一个 l r 和 a
a为even 表示 l~r 里面有偶数个1
思路有两种 1.用前缀和的想法 若 l ~ r为偶数 那么前缀 l - 1 与 前缀 r 的 1的个数相同 若l ~ r为奇数 则不同
2.存一个集合 fa[i] 表示 i为偶数 fa[i + n] 表示 为奇数
那么若 l ~ r为偶数 那么 fa[i] = fa[j] fa[i + n] = fa[j + n]
若为奇数 fa[i] = fa[j + n] fa[i + n] = fa[j + n]
关于线段树
线段树则需看清 条件要求的 是否可以由左儿子 右儿子 来递归完成
例如线段树维护区间最大公约数 由于公约数就是gcd(a1,a2,a3,a4…an)所以可以维护
若加上区间修改的话 那就需要用到性质 gcd(a1,a2…an) = gcd(a1,a2 - a1,a3 - a2…an - an - 1);
那么那么利用差分的性质 我们每次都只需要单点修改 就行了
线段树扫描线
主要求得问题是矩阵覆盖面积或者周长问题
步骤是
1.将所有的竖着得线段储存下来 并且按照从大到小排序 若数据有小数或者大数 则离散化
2.每一个矩阵得左边得边赋值为 1 右边的边赋值为2
3.从做往右遍历边 进行区间修改
4.注意!!! 由于扫描线是根据区间进行操作 列如 1 - 5 这个区间 + 1 的话 只需要将 1 - 4 + 1就行 因为 点1表示的其实是 1 ~ 2 表示的是1 ~ 2这段区间
周长的模板
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 2e4 + 100;
struct node{
int x,cnt;
}tree[N * 4];
struct edg{
int x,y1,y2,type;
bool operator < (const edg &p)const{
if(x == p.x) return type > p.type;
return x < p.x;
}
}edge[N];
struct ed{
int x1,x2,y,type;
bool operator < (const ed &p)const{
if(y == p.y) return type > p.type;
return y < p.y;
}
}edge2[N];
void build(int s,int t,int p){
tree[p].cnt = 0,tree[p].x = 0;
if(s == t) return;
int mid = s + t >> 1;
build(s,mid,p * 2);
build(mid + 1,t,p * 2 + 1);
}
void push_up(int s,int t,int p){
if(tree[p].cnt){
tree[p].x = (t - s + 1);
}else if(s != t){
tree[p].x = tree[p * 2].x + tree[p * 2 + 1].x;
}
else tree[p].x = 0;
}
void update(int s,int t,int l,int r,int p,int x){
if(s >= l && t <= r){
tree[p].cnt += x;
push_up(s,t,p);
return;
}
int mid = s + t>> 1;
if(l <= mid) update(s,mid,l,r,p * 2,x);
if(mid < r) update(mid + 1,t,l,r,p * 2 + 1,x);
push_up(s,t,p);
return;
}
int main(){
int n;
cin >> n;
int cnt = 0;
for(int i = 1; i <= n; i++){
int zx,zy,yx,yy;
cin >> zx >> zy >> yx >> yy;
zx += 10001,zy += 10001,yx += 10001,yy += 10001;
edge[++cnt] = {
zx,zy,yy,1};
edge2[cnt] = {
zx,yx,zy,1};
edge[++cnt] = {
yx,zy,yy,-1};
edge2[cnt] = {
zx,yx,yy,-1};
}
sort(edge + 1,edge + cnt + 1);
sort(edge2 + 1,edge2 + cnt + 1);
build(1,20001,1);
int sum = 0;
int now = 0,pre = 0;
for(int i = 1; i <= cnt; i++){
pre = tree[1].x;
update(1,20001,edge[i].y1,edge[i].y2 - 1,1,edge[i].type);
now = tree[1].x;
sum += abs(now - pre);
}
build(1,20001,1);
for(int i = 1; i <= cnt; i++){
pre = tree[1].x;
update(1,20001,edge2[i].x1,edge2[i].x2 - 1,1,edge2[i].type);
now = tree[1].x;
sum += abs(now - pre);
}
cout << sum << endl;
return 0;
}
面积并模板!
#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
const int N = (1e5 + 10) * 4;
typedef long long ll;
struct edge{
double x,y1,y2;int tape;
bool operator < (const edge &p)const{
return x < p.x;
}
}a[N];
double b[N];int cnt;
struct node{
double x;int cnt;
}tree[N];
void pushup(int s,int t,int p){
if(tree[p].cnt){
tree[p].x = b[t + 1] - b[s];
}else if(s != t){
tree[p].x = tree[p * 2].x + tree[p * 2 + 1].x;
}else tree[p].x = 0;
}
void update(int s,int t,int l,int r,int p,int x){
if(s >= l && t <= r){
tree[p].cnt += x;
pushup(s,t,p);
return;
}
int mid = s + t >> 1;
if(l <= mid) update(s,mid,l,r,p * 2,x);
if(mid < r) update(mid + 1,t,l,r,p * 2 + 1,x);
pushup(s,t,p);
return;
}
int main(){
int t,T = 0;
while(cin >> t && t){
cnt = 0;
for(int i = 1; i <= t; i++){
double x1,y1,x2,y2;
scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
b[++cnt] = y1,a[cnt] = {
x1,y1,y2,1};
b[++cnt] = y2,a[cnt] = {
x2,y1,y2,-1};
}
sort(b + 1,b + cnt + 1);
sort(a + 1,a + cnt + 1);
int tot = unique(b + 1,b + cnt + 1) - b - 1;
double res = 0;
for(int i = 1; i <= t * 2; i++){
int k1 = lower_bound(b + 1,b + tot + 1,a[i].y1) - b;
int k2 = lower_bound(b + 1,b + tot + 1,a[i].y2) - b;
if(i > 1){
res += tree[1].x * (a[i].x - a[i - 1].x);
}
update(1,tot,k1,k2 - 1,1,a[i].tape);
}
printf("Test case #%d\n", ++T);
printf("Total explored area: %.2lf\n\n", res);
}
return 0;
}
线段树的加减乘
#include<iostream>
using namespace std;
const int N = (1e5 + 10) * 4;
typedef long long ll;
int n,m;ll a[N];
ll tree[N],lazyc[N],lazyj[N];
void build(int s,int t,int p){
lazyc[p] = 1;
lazyj[p] = 0;
if(s == t){
tree[p] = a[s] % m;
return;
}
int mid = s + t >> 1;
build(s,mid,p * 2);
build(mid + 1,t,p * 2 + 1);
tree[p] = (tree[p * 2] + tree[p * 2 + 1]) % m;
return;
}
void pushdown(int s,int t,int p){
int mid = s + t >> 1;
tree[p * 2] = tree[p * 2] * lazyc[p] % m;
lazyj[p * 2] = lazyj[p * 2] * lazyc[p] % m;
lazyj[p * 2 + 1] = lazyj[p * 2 + 1] * lazyc[p] % m;
tree[p * 2 + 1] = tree[p * 2 + 1] * lazyc[p] % m;
lazyc[p * 2] = lazyc[p * 2] * lazyc[p] % m;
lazyc[p * 2 + 1] = lazyc[p * 2 + 1] * lazyc[p] % m;
lazyc[p] = 1;
tree[p * 2] = (tree[p * 2] + lazyj[p] * (mid - s + 1) % m) % m;
tree[p * 2 + 1] = (tree[p * 2 + 1] + lazyj[p] * (t - mid) % m) % m;
lazyj[p * 2] = (lazyj[p * 2] + lazyj[p]) % m;
lazyj[p * 2 + 1] = (lazyj[p * 2 + 1] + lazyj[p]) % m;
lazyj[p] = 0;
}
void pushup(int p){
tree[p] = (tree[p * 2] + tree[p * 2 + 1]) % m;
return;
}
void updatec(int s,int t,int l,int r,int p,ll x){
if(s >= l && t <= r){
tree[p] = tree[p] * x % m;
lazyc[p] = lazyc[p] * x % m;
lazyj[p] = lazyj[p] * x % m;
return;
}
pushdown(s,t,p);
int mid = s + t >> 1;
if(l <= mid) updatec(s,mid,l,r,p * 2,x);
if(mid < r) updatec(mid + 1,t,l,r,p * 2 + 1,x);
pushup(p);
return;
}
void updatej(int s,int t,int l,int r,int p,ll x){
if(s >= l && t <= r){
tree[p] = (tree[p] + (t - s + 1) * x % m) % m;
lazyj[p] = (lazyj[p] + x) % m;
return;
}
pushdown(s,t,p);
int mid = s + t >> 1;
if(l <= mid) updatej(s,mid,l,r,p * 2,x);
if(mid < r) updatej(mid + 1,t,l,r,p * 2 + 1,x);
pushup(p);
return;
}
ll getsum(int s,int t,int l,int r,int p){
ll sum = 0;
if(s >= l && t <= r){
return tree[p];
}
pushdown(s,t,p);
int mid = s + t >> 1;
if(l <= mid) sum = getsum(s,mid,l,r,p * 2) % m;
if(mid < r) sum = (sum + getsum(mid + 1,t,l,r,p * 2 + 1)) % m;
return sum;
}
int main(){
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; i++){
scanf("%lld",&a[i]);
}
build(1,n,1);
int t ;
cin >> t;
while(t--){
int op,x,y;ll z;
scanf("%d%d%d",&op,&x,&y);
if(op == 1){
scanf("%lld",&z);
updatec(1,n,x,y,1,z);
}else if(op == 2){
scanf("%lld",&z);
updatej(1,n,x,y,1,z);
}else{
printf("%lld\n",getsum(1,n,x,y,1));
}
}
return 0;
}
主席树 用来查询静态区间第k值
利用前缀和思想 分别存入 包括前 i 个树的 权值线段树
每次只需要修改 第i棵树 对于 第 i - 1个树的 不同的地方
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
struct node{
int l,r,v;
}tree[N * 25];
int a[N],b[N];
int tot,T[N];//T表示第i课树的 首结点
int build(int l,int r){
int p = ++tot;
if(l < r){
int mid = l + r >> 1;
tree[p].l = build(l,mid);
tree[p].r = build(mid + 1,r);
}
tree[p].v = 0;
return p;
}
int update(int pre,int l,int r,int x){
int p = ++tot;
tree[p].l = tree[pre].l,tree[p].r = tree[pre].r,tree[p].v = tree[pre].v + 1;
if(l < r){
int mid = l + r >> 1;
if(x <= mid) tree[p].l = update(tree[pre].l,l,mid,x);
else tree[p].r = update(tree[pre].r,mid + 1,r,x);
}
return p;
}
int query(int x,int y,int l,int r,int k){
if(l == r) return l;
int sum = tree[tree[y].l].v - tree[tree[x].l].v;
int mid = l + r >> 1;
if(k <= sum) return query(tree[x].l,tree[y].l,l,mid,k);
else return query(tree[x].r,tree[y].r,mid + 1,r,k - sum);
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; i++){
scanf("%d",&a[i]);
b[i] = a[i];
}
sort(b + 1,b + n + 1);
int cnt = unique(b + 1,b + n + 1) - b - 1;
T[0] = build(1,cnt);
for(int i = 1; i <= n; i++){
int s = lower_bound(b + 1,b + cnt + 1,a[i]) - b;
T[i] = update(T[i - 1],1,cnt,s);
}
for(int i = 1; i <= m; i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
printf("%d\n",b[query(T[x - 1],T[y],1,cnt,z)]); //求得是 x,y区间的第Z大值
}
return 0;
}
主席数求区间前k大值得前缀和
int build(int l,int r){
int p = ++tot;
tree[p].s = tree[p].val = tree[p].v = 0;
if(l < r){
int mid = l + r >> 1;
tree[p].l = build(l,mid);
tree[p].r = build(mid + 1,r);
}
return p;
}
int update(int pre,int l,int r,int x){
int p = ++tot;
tree[p].l = tree[pre].l,tree[p].r = tree[pre].r,tree[p].v = tree[pre].v + 1;
tree[p].s = tree[pre].s + m[x];
if(l == r){
tree[p].val = m[x];
return p;
}
if(l < r){
int mid = l + r >> 1;
if(x <= mid) tree[p].l = update(tree[pre].l,l,mid,x);
else tree[p].r = update(tree[pre].r,mid + 1,r,x);
}
return p;
}
ll query(int x,int y,int l,int r,int k){
if(l == r){
return tree[y].val * k; //防止k有很多个
}
int sum = tree[tree[y].r].v - tree[tree[x].r].v;
int mid = l + r >> 1;
if(sum >= k) return query(tree[x].r,tree[y].r,mid + 1,r,k); //倘若右边已经有k个那就直接往右边递归
else return query(tree[x].l,tree[y].l,l,mid,k - sum) + tree[tree[y].r].s - tree[tree[x].r].s; //倘若右边不够 那么就先把右边的值 加上 然后再去递归左边
}
trie树
假设需要存入 abcdef abcff abcki 等字符串
存1.那我们先从顶点查看是否有a这个点 若没有那么从a点加一个新节点b 然后加入cdef
存2.我们先看有a a里有b b里有 c c没有f 所以存入 f 然后f存入f
#include<iostream>
using namespace std;
const int maxn = 100010;
int son[maxn][26],cnt[maxn],idx;
void insert(char ch[]){
int p = 0;
for(int i = 0; ch[i]; i++){
int s = ch[i] - 'a';
if(!son[p][s]) son[p][s] = ++idx;
p = son[p][s];
}
cnt[p]++; //查看这个字符串出现了多少次
return;
}
int query(char ch[]){
int p = 0;
for(int i = 0; ch[i]; i++){
int s = ch[i] - 'a';
if(!son[p][s]) return 0;
p = son[p][s];
}
return cnt[p];
}
int main(){
int t;
cin >> t;
char a, ch[maxn];
while(t--){
cin >> a >> ch;
if(a == 'I'){
insert(ch);
}else{
cout << query(ch) << endl;
}
}
return 0;
}
可持久化trie树
用来查询区间 异或 与 x 的最大值
首先我们想想怎么找到与x异或的最大值 那么就是 利用trie树
每次查看是否有 与 二进制x 当前选择的 i 相反的 若有 那就选择那一条 进行 i + 1的查询
利用这个思想 我们查看区间的话 我们只需要 利用可持久化trie 树来限制 右边 因为 他是利用n棵 tire树 进行 存储的 所以 查询到R 只需要利用第 R棵trie树 我们已经限制了 R 怎么限制 L呢 我们只需要在查询的时候看 这个串 的最大出现的地方 是否大于L 即可 所以我们只需要每一次将 不同于上一棵树 的地方 新键一个 新的trie 树 赋值 就好了
#include<iostream>
using namespace std;
const int N = 6e5 + 10,M = N * 24;
int tr[M][2],a[N],root[M],max_id[M];
int tot;
void insert(int x,int k,int p,int q){
if(k == -1){
max_id[q] = x;
return;
}
int v = (a[x] >> k) & 1;
if(p) tr[q][v ^ 1] = tr[p][v ^ 1]; //如果有的话 那么另一半相同
tr[q][v] = ++tot; //创立一个新的 为了产生一个新的 max_id 为 x 得 串
insert(x,k - 1,tr[p][v],tr[q][v]);
max_id[q] = max(max_id[tr[q][0]],max_id[tr[q][1]]);
}
int query(int root,int C,int L){
int p = root;
for(int i = 23; i >= 0; i--){
int v = C >> i & 1;
if(max_id[tr[p][v ^ 1]] >= L) p = tr[p][v ^ 1];//若最大值大于L则进入这个 否则被迫进入另一边
else p = tr[p][v];
}
return C ^ a[max_id[p]];
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
root[0] = ++ tot;
max_id[0] = -1;
insert(0,23,0,root[0]);
for(int i = 1; i <= n; i++){
int x;
scanf("%d",&x);
a[i] = a[i - 1] ^ x;
root[i] = ++tot;
insert(i,23,root[i - 1],root[i]);
}
char op[3];
int l,r,x;
while(m--){
scanf("%s",op);
if(*op == 'A'){
scanf("%d",&x);
++n;
a[n] = a[n - 1] ^ x;
root[n] = ++tot;
insert(n,23,root[n - 1],root[n]);
}else{
scanf("%d%d%d",&l,&r,&x);
printf("%d\n",query(root[r - 1],a[n] ^ x,l - 1));
}
}
return 0;
}
散列表
存一个单链表
#include<iostream>
#include<cstring>
using namespace std;
const int N = 100003;
int head[N],last[N],to[N],cnt;
void insert(int x){
int k = (x % N + N) % N;
to[cnt] = x;
last[cnt] = head[k];
head[k] = cnt++;
}
bool query(int x){
int k = (x % N + N) % N;
for(int i = head[k]; i != -1; i = last[i]){
if(to[i] == x) return true;
}
return false;
}
int main(){
int n;
cin >> n;
char a[2];int x;
memset(head,-1,sizeof head);
for(int i = 1; i <= n; i++){
scanf("%s%d",a,&x);
if(a[0] == 'I') insert(x);
else{
if(query(x)) cout << "Yes" << endl;
else cout << "No" << endl;
}
}
return 0;
}
字符串哈希
#include<iostream>
using namespace std;
typedef unsigned long long ull;
const int maxn = 100010;
char ch[maxn];
ull h[maxn],p[maxn]; //ull 溢出自动mod 2^64 这个就是关键
int main(){
int n,m;
scanf("%d%d%s",&n,&m,ch + 1);
int s = 131;
p[0] = 1;
for(int i = 1; i <= n; i++){
p[i] = p[i - 1] * s;
h[i] = h[i - 1] * s + ch[i];
}
int x1,y1,x2,y2;
for(int i = 1; i <= m; i++){
cin >> x1 >> y1 >> x2 >> y2;
if(h[y1] - h[x1 - 1] * p[y1 - x1 + 1] == h[y2] - h[x2 - 1] * p[y2 - x2 + 1]){
cout << "Yes" << endl;
}else cout << "No" << endl;
}
return 0;
}
模拟堆
模拟堆模板
#include<iostream>
using namespace std;
const int maxn = 1e5 + 5;
string ch;int cnt = 0,h[maxn],ps1[maxn],ps2[maxn],s = 0;//ps1记录 第i个插入的数的位置,ps2记录 这个位置的数是第几个插入的;
void swap_h(int x,int y){
swap(h[x],h[y]);
swap(ps2[x],ps2[y]);
swap(ps1[ps2[x]],ps1[ps2[y]]);
}
void down(int x){
int t = x;
if(x * 2 <= cnt && h[t] > h[x * 2]) t = x * 2;
if(x * 2 + 1 <= cnt && h[t] > h[x * 2 + 1]) t = x * 2 + 1;
if(t != x){
swap_h(x,t);
down(t);
}
}
void up(int x){
if(x / 2 > 0 && h[x / 2] > h[x]){
swap_h(x,x / 2);
up(x >> 1);
}
}
void insert(int x){
s++;
h[++cnt] = x;
ps1[s] = cnt;
ps2[cnt] = s;
up(cnt);
}
void output(){
cout << h[1] << endl;
}
void change(int x,int y){
h[ps1[x]] = y;
up(ps1[x]);
down(ps1[x]);
}
int main(){
int n;
cin >> n;
int x,y;
for(int i = 1; i <= n; i++){
cin >> ch;
if(ch == "I"){
cin >> x;
insert(x);
}else if(ch == "PM"){
output();
}else if(ch == "DM"){
swap_h(1,cnt);
cnt--;
down(1);
}else if(ch == "D"){
cin >> x;
int u = ps1[x];
swap_h(u,cnt);
cnt--;
up(u);
down(u);
}else{
cin >> x >> y;
change(x,y);
}
}
return 0;
}
莫队模板
#include <stdio.h>
#include<cmath>
#include <vector>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <stack>
#define ll long long
#define chushi(a, b) memset(a, b, sizeof(a))
#define endl "\n"
const double eps = 1e-8;
const ll INF=0x3f3f3f3f;
const int mod=1e9 + 7;
const int maxn = 3e5 + 5;
const int N=1005;
using namespace std;
int a[maxn], ANS = 0, tm[maxn], cnt[maxn], ans[maxn];
typedef struct Node{
int l, r;
int id;
} node;
int sqt;
bool cmp(node A, node B){
if(A.l/sqt == B.l/sqt) return A.l/sqt%2 == 1 ? A.r < B.r : A.r > B.r;
else return A.l/sqt < B.l/sqt;
}
node q[maxn];
void add(int num){
--cnt[tm[num]];
ANS = max(ANS, ++tm[num]);
++cnt[tm[num]];
}
void subd(int num){
--cnt[tm[num]];
if(ANS == tm[num] && cnt[tm[num]] == 0) ANS--;
--tm[num], ++cnt[tm[num]];
}
int main() {
int n, m;
scanf("%d %d", &n, &m);
sqt = sqrt(n);
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
for(int i = 1; i <= m; i++) scanf("%d %d", &q[i].l, &q[i].r);
for(int i = 1; i <= m; i++) q[i].id = i;
sort(q+1, q+1+m, cmp);
for(int l = 1, r = 0, i = 1; i <= m; i++){
int ln = q[i].l, rn = q[i].r;
while(l < ln) subd(a[l++]);
while(l > ln) add(a[--l]);
while(r < rn) add(a[++r]);
while(r > rn) subd(a[r--]);
ans[q[i].id] = 1;
if(ANS > (rn-ln+2)/2) ans[q[i].id] = 2 * ANS - (rn-ln+1);
}
for(int i = 1; i <= m; i++) printf("%d\n", ans[i]);
return 0;
}
双向链表
ll to[N],last[N],w[N];
void addl(int x,int y,ll z){
to[last[y]] = x;
last[x] = last[y];
to[x] = y;
w[x] = z;
last[y] = x;
}
void addr(int x,int y,ll z){
last[to[y]] = x;
to[x] = to[y];
to[y] = x;
w[x] = z;
last[x] = y;
}
void del(int x){
to[last[x]] = to[x];
last[to[x]] = last[x];
to[x] = 0;
w[x] = 0;
last[x] = 0;
}
树链剖分模板
树链剖分 就是将树上的节点利用时间戳 来将子树或者链 变成区间的形式去实现 加减 有几个重要的概念 1.重儿子 是每个点的儿子中 子树大小最大的 (若有多个儿子子树大小相同 且 最大 那么就随便选) 2.轻儿子 除了重儿子 所有的点 3.重边就是重儿子和他父亲节点连的边 4.重链由多个重边组成的链
fa[N] 保存每个点的父亲节点
top[N]保存每个重链的顶点
id[N]保存每个点的时间戳
depth[N]保存每个点的深度
son[N]保存每个点的重儿子
首先dfs遍历一遍找出每个子树的大小和重儿子 和深度
然后再跑第二个dfs 知道每条重链的顶
首先我们可以想到 因为 当前点的重儿子 肯定是和 当前点的重链的顶是一样的 为了保证重链的边是连续的所以我们首先跑重儿子 其次再跑轻儿子 这样就可以利用 类似lca的思想 每次查询当前top[u]和top[v]是否相同 如果相同的话 那么他们的点在同一条重链上就可以退出了 若不在同一条重链上首先判断哪个点的depth小 小的那位往上跑 并且将这一次的重链上的边都修改掉
图中的红线即是重边 2 3 4 7 是重儿子 若想让 4 7的路径上 都加上1的话
4的顶是1 7的顶是6 所以把7 - 6先修改掉 然后 v = fa[top[v]]
然后4的顶是 1 1的顶是 1 所以他们的链相同 此时depth[1]小于depth[4]所以修改 (1,4)就好了
#include<iostream>
#include<cstring>
using namespace std;
const int N = 1e5 + 10,M = 2e5 + 10;
typedef long long ll;
int head[N],to[M],last[M],w[M],cnt;
void add(int a,int b){
to[++cnt] = b;
last[cnt] = head[a];
head[a] = cnt;
}
struct node{
int l,r;ll add,sum;
}tree[N * 4];
int sz[N],nw[N];
int top[N],id[N],depth[N],times,fa[N],son[N];
void dfs1(int x,int father){
sz[x] = 1;
depth[x] = depth[father] + 1;
fa[x] = father;
for(int i = head[x]; i != -1; i = last[i]){
int j = to[i];
if(j == father) continue;
dfs1(j,x);
sz[x] += sz[j];
if(sz[son[x]] < sz[j]){
son[x] = j;
}
}
}
void dfs2(int x,int tp){
top[x] = tp;
id[x] = ++times;
nw[times] = w[x];
if(!son[x]) return;
dfs2(son[x],tp);
for(int i = head[x]; i != -1; i = last[i]){
int j = to[i];
if(j == fa[x] || j == son[x]) continue;
dfs2(j,j);
}
}
void pushup(int p){
tree[p].sum = tree[p * 2].sum + tree[p * 2 + 1].sum;
}
void pushdown(int p){
tree[p * 2].add += tree[p].add;
tree[p * 2].sum += tree[p].add * (tree[p * 2].r - tree[p * 2].l + 1);
tree[p * 2 + 1].add += tree[p].add;
tree[p * 2 + 1].sum += tree[p].add * (tree[p * 2 + 1].r - tree[p * 2 + 1].l + 1);
tree[p].add = 0;
}
void build(int l,int r,int p){
tree[p].l = l,tree[p].r = r,tree[p].sum = nw[r];
if(l == r) return;
int mid = l + r >> 1;
build(l,mid,p * 2);
build(mid + 1,r,p * 2 + 1);
pushup(p);
}
void update(int l,int r,int p,int k){
if(tree[p].l >= l && tree[p].r <= r){
tree[p].add += k;
tree[p].sum += (tree[p].r - tree[p].l + 1) * k;
return;
}
pushdown(p);
int mid = tree[p].l + tree[p].r >> 1;
if(l <= mid) update(l,r,p * 2,k);
if(mid < r) update(l,r,p * 2 + 1,k);
pushup(p);
}
ll query(int l,int r,int p){
if(tree[p].l >= l && tree[p].r <= r){
return tree[p].sum;
}
pushdown(p);
int mid = tree[p].l + tree[p].r >> 1;
ll sum = 0;
if(l <= mid) sum = query(l,r,p * 2);
if(mid < r) sum += query(l,r,p * 2 + 1);
return sum;
}
void update_path(int u,int v,int k){
while(top[u] != top[v]){
if(depth[top[u]] < depth[top[v]]) swap(u,v);
update(id[top[u]],id[u],1,k);
u = fa[top[u]];
}
if(depth[u] > depth[v]) swap(u,v);
update(id[u],id[v],1,k);
}
ll query_path(int u,int v){
ll sum = 0;
while(top[u] != top[v]){
if(depth[top[u]] < depth[top[v]]) swap(u,v);
sum += query(id[top[u]],id[u],1);
u = fa[top[u]];
}
if(depth[u] > depth[v]) swap(u,v);
sum += query(id[u],id[v],1);
return sum;
}
void update_tree(int u,int k){
update(id[u],id[u] + sz[u] - 1,1,k);
}
ll query_tree(int u){
return query(id[u],id[u] + sz[u] - 1,1);
}
int main(){
int n;
cin >> n;
for(int i = 1; i <= n; i++){
scanf("%d",&w[i]);
}
memset(head,-1,sizeof head);
for(int i = 1; i <= n - 1; i++){
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
dfs1(1,0);
dfs2(1,1);
build(1,n,1);
int m;
cin >> m;
while(m--){
int op,u,v,k;
scanf("%d",&op);
if(op == 1){
scanf("%d%d%d",&u,&v,&k);
update_path(u,v,k);
}else if(op == 2){
scanf("%d%d",&u,&k);
update_tree(u,k);
}else if(op == 3){
scanf("%d%d",&u,&v);
printf("%lld\n",query_path(u,v));
}else{
scanf("%d",&u);
printf("%lld\n",query_tree(u));
}
}
return 0;
}
左偏树
左偏树概念为左右子树都比根结点大 左边子树大小比右边子树大小大 即根节点到非空节点的大小为右边子树的最浅 + 1
#include<iostream>
#include<cstring>
using namespace std;
const int N = 2e5 + 10;
struct node{
int v,dis,l,r;
}tree[N];
int fa[N],idx;
int get(int x){
if(fa[x] == x) return x;
return fa[x] = get(fa[x]);
}
bool cmp(int x,int y){
if(tree[x].v == tree[y].v){
return x < y;
}
return tree[x].v < tree[y].v;
}
int merge(int x,int y){
if(!x || !y) return x + y;
if(cmp(y,x)) swap(x,y);
tree[x].r = merge(tree[x].r,y);
if(tree[tree[x].r].dis > tree[tree[x].l].dis) swap(tree[x].l,tree[x].r);
tree[x].dis = tree[tree[x].r].dis + 1;
return x;
}
int main(){
int t;
cin >> t;
tree[0].v = 2e9;
while(t--){
int op,x,y;
scanf("%d%d",&op,&x);
if(op == 1){
tree[++idx].v = x;
tree[idx].dis = 1;
fa[idx] = idx;
}else if(op == 2){
scanf("%d",&y);
int fx = get(x),fy = get(y);
if(fx != fy){
if(cmp(fy,fx)) swap(fx,fy);
fa[fy] = fx;
merge(fx,fy);
}
}else if(op == 3){
cout << tree[get(x)].v << endl;
}else{
int fx = get(x);
if(cmp(tree[fx].r,tree[fx].l)) swap(tree[fx].r,tree[fx].l);
fa[fx] = tree[fx].l;
fa[tree[fx].l] = tree[fx].l;
merge(tree[fx].l,tree[fx].r);
}
}
return 0;
}
克鲁斯卡儿重构树
顾名思义 使用最小生成树来建成的树 每次选择边权最小的边 往里面加 每两个连通块联通至新加的一个点 让他们的祖先连向 这个点 那么 每次都会让边权更大的边 体现出的那个点 连向 边权小的点
for(int i = 1; i <= m; i++){
int x = get(edge[i].x),y = get(edge[i].y);
if(x != y){
fa[x] = fa[y] = ++ext;
v[ext] = edge[i].w;
add(ext,x);
add(ext,y);
}
}