树状数组

思路

将数组用\(lowbit(x)\)​​​​划分,可以支持单点修改与区间查询,时间复杂度为\(O(n\log n)\)​​,常数小于线段树,不过可操作的内容较少,使用也较少。树状数组经过一些奇特的改造以后可以支持区间修改与单点查询,时间复杂度仍为\(O(n\log n)\)​,但是只支持区间加减操作。

例如:

\[\begin{cases} tree[1]=a[1]\\ tree[2]=a[1]+a[2]\\ tree[3]=a[3]\\ tree[4]=a[1]+a[2]+a[3]+a[4]\\ tree[5]=a[5]\\ tree[6]=a[5]+a[6]\\ tree[7]=a[7]\\ tree[8]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8] \end{cases} \]

树状数组 \(1\)

思路

树状数组版题,单点修改区间查询。

\(Code\)

#include<bits/stdc++.h>
using namespace std;

int n, m, tree[500005], a;
int x, y, num, op;

template<typename T>
inline void read(T&x){
	x = 0; char q; bool f = 1;
	while(!isdigit(q = getchar()))	if(q == '-')	f = 0;
	while(isdigit(q)){
		x = (x<<1) + (x<<3) + (q^48);
		q = getchar();
	}
	x = f?x:-x;
}

template<typename T>
inline void write(T x){
	if(x < 0){
		putchar('-');
		x = -x;
	}
	if(x > 9)	write(x/10);
	putchar(x%10+'0');
}


int lowbit(int u){
	return u & -u;
}

void add(int u, int k){
	while(u <= n){
		tree[u] += k;
		u += lowbit(u);
	}
}

void sum(int u, int v){
	int sum1 = 0, sum2 = 0;
	while(u > 0){
		sum1 += tree[u];
		u -= lowbit(u);
	}
	while(v > 0){
		sum2 += tree[v];
		v -= lowbit(v);
	}
	write(sum2 - sum1);
	putchar('\n');
}

int main(){
	read(n), read(m);
	for(int i = 1; i <= n; i++){
		read(num);
		add(i, num);
	}
	for(int i = 1; i <= m; i++){
		read(op);
		if(op == 1){
			read(x), read(num);
			add(x, num);
		}
		if(op == 2){
			read(x), read(y);
			sum(x-1, y);
		}
	}
	return 0;
}

统计和

思路

单点修改区间查询。

\(Code\)

#include<bits/stdc++.h>
using namespace std;

int n, m;
long long tree[500005];
char op;
int x, y;

template<typename T>
inline void read(T&x){
    x = 0; char q; bool f = 1;
    while(!isdigit(q = getchar()))  if(q == '-')    f = 0;
    while(isdigit(q)){
        x = (x<<1) + (x<<3) + (q^48);
        q = getchar();
    }
    x = f?x:-x;
}

template<typename T>
inline void write(T x){
    if(x < 0){
        putchar('-');
        x = -x;
    }
    if(x > 9)	write(x/10);
    putchar(x%10+'0');
}

inline int lowbit(int x){
	return x&(-x);
}

inline void add(int u, int num){
	while(u <= n){
		tree[u] += num;
		u += lowbit(u);
	}
}

inline long long search(int u){
	long long ans = 0;
	while(u){
		ans += tree[u];
		u -= lowbit(u);
	}
	return ans;
}

int main(){
	read(n), read(m);
	for(register int i = 1; i <= m; ++i){
		scanf("%c", &op);
		if(op == 'x'){
			read(x), read(y);
			add(x, y);
		}
		if(op == 'y'){
			read(x), read(y);
			write(search(y)-search(x-1));
			puts("");
		}
	}
    return 0;
}

树状数组 \(2\)​​

思路

区间修改,单点查询,很容易想到用差分数组使其变为单点修改区间查询。

\(Code\)

#include<bits/stdc++.h>
using namespace std;

int n, m, tree[500005], a[500005];
int x, y, num, op;

template<typename T>
inline void read(T&x){
	x = 0; char q; bool f = 1;
	while(!isdigit(q = getchar()))	if(q == '-')	f = 0;
	while(isdigit(q)){
		x = (x<<1) + (x<<3) + (q^48);
		q = getchar();
	}
	x = f?x:-x;
}

template<typename T>
inline void write(T x){
	if(x < 0){
		putchar('-');
		x = -x;
	}
	if(x > 9)	write(x/10);
	putchar(x%10+'0');
}

inline int lowbit(int u){
	return u & -u;
}

inline void add(int u, int k){
	while(u <= n){
		tree[u] += k;
		u += lowbit(u);
	}
}

inline int sum(int u){
	int sum = 0;
	while(u > 0){
		sum += tree[u];
		u -= lowbit(u);
	}
	return sum;
}

int main(){
	read(n), read(m);
	for(register int i = 1; i <= n; ++i){
		read(a[i]);
		add(i, a[i]-a[i-1]);
	}
	for(register int i = 1; i <= m; ++i){
		read(op);
		if(op == 1){
			read(x), read(y), read(num);
			add(x, num);
			add(y+1, -num);
		}
		if(op == 2){
			read(x);
			write(sum(x));
			putchar('\n');
		}
	}
	return 0;
}

线段树 \(1\)

思路

区间修改,区间查询,首先还是考虑差分。

\(a\)是原数组,\(c\)是差分数组,那么\(a[1]=c[1],a[2]=c[1]+c[2],\ldots,a[n]=c[1]+c[2]+\ldots+c[n]\)

所以\(\sum^n_{i}{a[i]}=c[1]+(c[1]+c[2])+\ldots+(c[1]+c[2]+\ldots+c[i])\)​。​

\(c1[i]=(i-1)\times c[i]\),则\(\sum^n_i{a[i]}=i\times\sum^n_i{c[i]}-\sum^n_i{c1[i]}\)

预处理出\(c[i]\)\(c1[i]\),用两个树状数组分别维护即可。

\(Code\)

#include<bits/stdc++.h>
#define int long long
using namespace std;

int n, m, a[200005], tree[200005], c[200005], tree1[200010], c1[200010];
int x, y, k;
int op;

template<typename T>
inline void read(T&x){
	x = 0; char q; bool f = 1;
	while(!isdigit(q = getchar()))	if(q == '-')	f = 0;
	while(isdigit(q)){
		x = (x<<1) + (x<<3) + (q^48);
		q = getchar();
	}
	x = f?x:-x;
}

template<typename T>
inline void write(T x){
	if(x < 0){
		putchar('-');
		x = -x;
	}
	if(x > 9)	write(x/10);
	putchar(x%10+'0');
}

inline int lowbit(int x){
	return x & -x;
}

inline void modify(int x, int y){
	for(register int i = x; i <= n; i += lowbit(i))
		tree[i] += y;
}

inline int query(int x){
	int ans = 0;
	for(register int i = x; i > 0; i -= lowbit(i))
		ans += tree[i];
	return ans;
}

inline void modify1(int x, int y){
	for(register int i = x; i <= n; i += lowbit(i))
		tree1[i] += y;
}

inline int query1(int x){
	int ans = 0;
	for(register int i = x; i > 0; i -= lowbit(i))
		ans += tree1[i];
	return ans;
}

signed main(){
	read(n), read(m);
	for(register int i = 1; i <= n; ++i){
		read(a[i]);
		c[i] = a[i] - a[i-1];
		c1[i] = (i-1)*c[i];
	}
	for(register int i = 1; i <= n; ++i)	modify(i, c[i]);
	for(register int i = 1; i <= n; ++i)	modify1(i, c1[i]);
	for (int i=1;i<=m;i++){
		read(op);
		if(op == 1){
			read(x), read(y), read(k);
			modify(x, k);
			modify(y+1, -k);
			modify1(x, (x-1)*k);
			modify1(y+1, -y*k);
		}
		if(op == 2){
			read(x), read(y);
			int ans = (y*query(y)-query1(y))-((x-1)*query(x-1)-query1(x-1));
			write(ans);
			putchar('\n');
		}
	}
	return 0;
}

逆序对

思路

首先离散化,按顺序一次往里面加入数,每次查找有多少个数已加入且大于等于当前数即可。

\(Code\)

#include<bits/stdc++.h>
using namespace std;

int n;
long long a[500005], q[500005];
int tree[500005];
long long t = 0;

template<typename T>
inline void read(T&x){
	x = 0; char q; bool f = 1;
	while(!isdigit(q = getchar()))	if(q == '-')	f = 0;
	while(isdigit(q)){
		x = (x<<1) + (x<<3) + (q^48);
		q = getchar();
	}
	x = f?x:-x;
}

template<typename T>
inline void write(T x){
	if(x < 0){
		putchar('-');
		x = -x;
	}
	if(x > 9)	write(x/10);
	putchar(x%10+'0');
}

inline int lowbit(int x){
	return x & -x;
}

inline void add(int x){
	while(x <= n){
		tree[x]++;
		x += lowbit(x);
	}
}

inline int ask(int x){
	int sum = 0;
	while(x != 0){
		sum += tree[x];
		x -= lowbit(x);
	}
	return sum;
}

int main(){
	read(n);
	for(register int i = 1; i <= n; ++i){
		read(q[i]);
		a[i] = q[i];
	}
	sort(q+1, q+n+1);
	int tot = unique(q+1,q+n+1) - (q+1);
	for(register int i = 1; i <= n; ++i)
		a[i] = lower_bound(q+1, q+tot+1, a[i]) - q;
	for(register int i = n; i >= 1; --i){
		t += ask(a[i]-1);
		add(a[i]);
	}
	write(t);
	return 0;
}

火柴排队

思路

可以很容易就发现这道题在两个序列对应的数大小编号相同时就是最优解了,离散化后如果把一的大小编号运用到二中,那么逆序对个数就是最终解的个数。

\(Code\)

#include<bits/stdc++.h>
using namespace std;

const int mod = 1e8-3;
int n;
int c[100005];
struct node{
	int num, pos;
}a[100005], b[100005];
int tree[100005];
int t = 0;

template<typename T>
inline void read(T&x){
	x = 0; char q; bool f = 1;
	while(!isdigit(q = getchar()))	if(q == '-')	f = 0;
	while(isdigit(q)){
		x = (x<<1) + (x<<3) + (q^48);
		q = getchar();
	}
	x = f?x:-x;
}

template<typename T>
inline void write(T x){
	if(x < 0){
		putchar('-');
		x = -x;
	}
	if(x > 9)	write(x/10);
	putchar(x%10+'0');
}

int lowbit(int x){
	return x & -x;
}

void add(int x){
	while(x <= n){
		tree[x]++;
		x += lowbit(x);
	}
}

int ask(int x){
	int sum = 0;
	while(x != 0){
		sum += tree[x];
		x -= lowbit(x);
	}
	return sum;
}

inline bool cmp(node x, node y){
	return x.num < y.num;
}

int main(){
	read(n);
	for(register int i = 1; i <= n; ++i){
		read(a[i].num);
		a[i].pos = i;
	}
	for(register int i = 1; i <= n; ++i){
		read(b[i].num);
		b[i].pos = i;
	}
	sort(a+1, a+n+1, cmp);
	sort(b+1, b+n+1, cmp);
	for(register int i = 1; i <= n; ++i)	c[a[i].pos] = b[i].pos;
	for(register int i = n; i >= 1; --i){
		t = (t+ask(c[i]-1))%mod;
		add(c[i]);
	}
	write(t);
	return 0;
}

简单题

思路

维护一个差分序列,求一个点的值就是求其异或前缀和。

\(Code\)

#include<bits/stdc++.h>
using namespace std;

int n, m, tree[100005];
int op, x, y;

template<typename T>
inline void read(T&x){
	x = 0; char q; bool f = 1;
	while(!isdigit(q = getchar()))	if(q == '-')	f = 0;
	while(isdigit(q)){
		x = (x<<1) + (x<<3) + (q^48);
		q = getchar();
	}
	x = f?x:-x;
}

template<typename T>
inline void write(T x){
	if(x < 0){
		putchar('-');
		x = -x;
	}
	if(x > 9)	write(x/10);
	putchar(x%10^48);
}

inline int lowbit(int x){
	return x&(-x);
}

inline void modify(int u){
	while(u <= n){
		tree[u] ^= 1;
		u += lowbit(u);
	}
}

inline bool search(int u){
	int ans = 0;
	while(u){
		ans ^= tree[u];
		u -= lowbit(u);
	}
	return ans;
}

int main(){
	read(n), read(m);
	while(m--){
		read(op);
		if(op == 1){
			read(x), read(y);
			modify(x), modify(y+1);
		}
		else{
			read(x);
			write(search(x));
			puts("");
		}
	}
	return 0;
}

HH的项链

思路

经典区间数颜色离线算法,当然如果离线的话线段树等等都能写,在线好像只有主席树比较容易实现了。

将询问按照 \(r\) 排序,树状数组 \(tree_i\) 表示 \(1\) ~ \(i\) 出现的不同数字的个数,每遇到一个新的就在这个新的这里加一,如果以前这个点出现过就把以前的贡献去掉,答案就是前缀和思想 \(tree_r-tree_{l-1}\)

处理的时候是 \(O(n\log n)\) 的,也就是对整一个数组跑一遍,时间复杂度 \(O(n\log n+m\log m)\)

\(Code\)

#include<bits/stdc++.h>
using namespace std;

int n, m;
int a[1000005];
struct node{
	int id, l, r;
}ask[1000005];
int tree[1000005];
int last[1000005], ne = 1;
int ans[1000005];

template<typename T>
inline void read(T&x){
	x = 0; char q; bool f = 1;
	while(!isdigit(q = getchar()))	if(q == '-')	f = 0;
	while(isdigit(q)){
		x = (x<<1) + (x<<3) + (q^48);
		q = getchar();
	}
	x = f?x:-x;
}

template<typename T>
inline void write(T x){
	if(x < 0){
		putchar('-');
		x = -x;
	}
	if(x > 9)	write(x/10);
	putchar(x%10^48);
}

inline bool cmp(node a, node b){
	return a.r < b.r;
}

inline int lowbit(int u){
	return u&(-u);
}

inline void modify(int u, int num){
	while(u <= n){
		tree[u] += num;
		u += lowbit(u);
	}
}

inline int query(int u){
	int ans = 0;
	while(u){
		ans += tree[u];
		u -= lowbit(u);
	}
	return ans;
}

int main(){
	read(n);
	for(register int i = 1; i <= n; ++i)	read(a[i]);
	read(m);
	for(register int i = 1; i <= m; ++i){
		read(ask[i].l), read(ask[i].r);
		ask[i].id = i;
	}
	sort(ask+1, ask+m+1, cmp);
	for(register int i = 1; i <= m; ++i){
		for(register int j = ne; j <= ask[i].r; ++j){
			if(last[a[j]])	modify(last[a[j]], -1);
			modify(j, 1);
			last[a[j]] = j;
		}
		ne = ask[i].r+1;
		ans[ask[i].id] = query(ask[i].r)-query(ask[i].l-1);
	}
	for(register int i = 1; i <= m; ++i)	write(ans[i]), puts("");
	return 0;
}
posted @ 2024-10-09 18:20  Zzzzzzzm  阅读(21)  评论(0)    收藏  举报