zoukankan      html  css  js  c++  java
  • 「数据结构」第2章 树状数组课堂过关

    「数据结构」第2章 树状数组课堂过关

    A. 【例题1】单点修改区间查询

    题目

    代码

    #include <iostream>
    #include <cstdio>
    using namespace std;
    #define N 1000010
    #define ll long long
    int read() {
    	int re = 0;
    	char c = getchar();
    	bool sig = false;
    	while(c < '0' || c > '9') {
    		if(c == '-')	sig = true;
    		c = getchar();
    	}
    	while(c >= '0' && c <= '9')
    		re  = (re << 1) + (re << 3) + c - '0' , c = getchar();
    	return sig ? -re : re;
    }
    struct node {
    	int siz;
    	ll a[N * 2];
    	#define lowbit(_) ((_) & -(_))
    	void add(int i , ll dat) {
    		for( ; i <= siz ; i += lowbit(i))
    			a[i] += dat;
    	}
    	ll getsum(int r) {
    		ll sum = 0;
    		for( ; r ; r -= lowbit(r))
    			sum += a[r];
    		return sum;
    	}
    }tarray;
    int n , q;
    int main() {
    	n = read(); q = read();
    	tarray.siz = n;
    	for(int i = 1 ; i <= n ; i++)
    		tarray.add(i , read());
    	while(q--) {
    		int ty = read() , d1 = read() , d2 = read();
    		if(ty == 1)
    			tarray.add(d1 , d2);
    		else
    			printf("%lld
    " , tarray.getsum(d2) - tarray.getsum(d1 - 1));
    	}
    	return 0;
    }
    

    B. 【例题2】逆序对

    题目

    代码

    模板题,不多解释

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    
    #define nn 500010
    using namespace std;
    int read() {
    	int re = 0;
    	char c = getchar();
    	while(c < '0' || c > '9')c = getchar();
    	while(c >= '0' && c <= '9')
    		re = (re << 1) + (re << 3) + c - '0',
    		c = getchar();
    	return re;
    }
    struct TreeArray {
    	int dat[nn * 2];
    	int siz;
    	#define lowbit(_) ((_) & -(_))
    	inline void updata(int d , int poi) {
    		for( ; poi <= siz ; poi += lowbit(poi))
    			dat[poi] += d;
    	}
    	inline int getsum(int r) {
    		int sum = 0;
    		for( ; r > 0 ; r -= lowbit(r))
    			sum += dat[r];
    		return sum;
    	}
    }t;
    
    
    struct node {
    	int id , dat;
    };
    bool cmp(node a , node b) {
    	return a.dat < b.dat;
    }
    node tmp[nn];
    void Discretize(int *st , int *ed) {
    	int n = ed - st;
    	for(int i = 0 ; i < n ; i++)
    		tmp[i].id = i , tmp[i].dat = st[i];
    	sort(tmp , tmp + n , cmp);
    	
    	int cnt = 1;
    	st[tmp[0].id] = cnt;
    	for(int i = 1 ; i < n ; i++) {
    		if(tmp[i].dat != tmp[i - 1].dat)	++cnt;
    		st[tmp[i].id] = cnt;
    	}
    }
    
    
    int a[nn];
    int n;
    int main() {
    	n = t.siz = read();
    	for(int i = 1 ; i <= n ; i++)
    		a[i] = read();
    	Discretize(a + 1 , a + n + 1);
    	
    	long long ans = 0;
    	for(int i = n ; i >= 1 ; i--) {
    		ans += t.getsum(a[i] - 1);
    		t.updata(1 , a[i]);
    	}
    	cout << ans;
    	return 0;
    }
    

    C. 【例题3】严格上升子序列数

    题目

    思路&代码

    是道好题

    1

    这题的DP并不难想,设(f_{i,j})表示以(i)为结束点,长度为(j)的严格上升子序列的数量,则(f_{i,1}=1),答案为(sum^n_{i=1}f_{i,m})

    状态转移:

    [f_{i,j}=sum f_{k,j-1}(a_k < iand k<i) ]

    写成代码:

    		memset(f , 0 , sizeof(f));
    		for(int i = 1 ; i <= n ; i++)
    			f[i][1] = 1;
    		for(int i = 1 ; i <= n ; i++)//这里先枚举i,j都是一样的
    			for(int j = 2 ; j <= m ; j++)
    				for(int k = 1 ; k < i ; k++)
    					if(a[k] < a[i])
    						f[i][j] += f[k][j - 1];
    		int ans = 0;
    		for(int i = 1 ; i <= n ; i++)
    			ans += f[i][m];
    

    时间为(O(n^2m))

    2

    考虑优化

    按照逆序对的思想,将(a)离散化后,从1到(n)枚举,(a_i)(f)的下标,就可以不用判断(a_k < a_i),为嵌入数据结构打下基础

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    #define N 1010
    #define ll long long
    int read() {
    	int re = 0;
    	char c = getchar();
    	bool sig = false;
    	while(c < '0' || c > '9') {
    		if(c == '-')	sig = true;
    		c = getchar();
    	}
    	while(c >= '0' && c <= '9')
    		re  = (re << 1) + (re << 3) + c - '0' , c = getchar();
    	return sig ? -re : re;
    }
    
    
    struct node {
    	int id , dat;
    };
    bool cmp(node a , node b) {
    	return a.dat < b.dat;
    }
    node tmp[N];
    void Discretize(int *st , int *ed) {
    	int n = ed - st;
    	for(int i = 0 ; i < n ; i++)
    		tmp[i].id = i , tmp[i].dat = st[i];
    	sort(tmp , tmp + n , cmp);
    	
    	int cnt = 1;
    	st[tmp[0].id] = cnt;
    	for(int i = 1 ; i < n ; i++) {
    		if(tmp[i].dat != tmp[i - 1].dat)	++cnt;
    		st[tmp[i].id] = cnt;
    	}
    }
    int a[N];
    int n , m , T;
    int f[N][N];
    int main() {
    	T = read();
    	for(int eee = 1 ; eee <= T ; eee++) {
    		n = read() , m = read();
    		for(int i = 1 ; i <= n ; i++)
    			a[i] = read();
    		
    		Discretize(a + 1 , a + n + 1);
    		memset(f , 0 , sizeof(f));
    		for(int i = 1 ; i <= n ; i++)  {
    			f[a[i]][1] += 1;
    			for(int j = 2 ; j <= m ; j++) {
    				for(int k = 1 ; k < a[i] ; k++)//这里不是赤裸裸的可以套数据结构吗
    					f[a[i]][j] += f[k][j - 1];
    			}
    		}
    		int ans = 0;
    		for(int i = 1 ; i <= n ; i++)
    			ans += f[i][m];
    		printf("%d
    " , ans);
    	}
    	return 0;
    }
    

    3

    嵌入树状数组就可以AC啦

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    #define N 1010
    #define ll long long
    #define mod 1000000007ll
    int read() {
    	int re = 0;
    	char c = getchar();
    	bool sig = false;
    	while(c < '0' || c > '9') {
    		if(c == '-')	sig = true;
    		c = getchar();
    	}
    	while(c >= '0' && c <= '9')
    		re  = (re << 1) + (re << 3) + c - '0' , c = getchar();
    	return sig ? -re : re;
    }
    
    
    struct node {
    	int id , dat;
    };
    bool cmp(node a , node b) {
    	return a.dat < b.dat;
    }
    node tmp[N];
    void Discretize(int *st , int *ed) {
    	int n = ed - st;
    	for(int i = 0 ; i < n ; i++)
    		tmp[i].id = i , tmp[i].dat = st[i];
    	sort(tmp , tmp + n , cmp);
    	
    	int cnt = 1;
    	st[tmp[0].id] = cnt;
    	for(int i = 1 ; i < n ; i++) {
    		if(tmp[i].dat != tmp[i - 1].dat)	++cnt;
    		st[tmp[i].id] = cnt;
    	}
    }
    int a[N];
    int n , m , T;
    struct TreeArray {
    	int siz;
    	ll a[N * 2];
    	#define lowbit(_) ((_) & -(_))
    	void clear() {
    		siz = n;
    		memset(a , 0 , sizeof(a));
    	}
    	void add(int i , ll dat) {
    		for( ; i <= siz ; i += lowbit(i))
    			a[i] = (a[i] + dat) % mod;
    	}
    	ll getsum(int r) {
    		ll sum = 0;
    		for( ; r ; r -= lowbit(r))
    			sum = (sum + a[r]) % mod;
    		return sum;
    	}
    }f[N];
    int main() {
    	T = read();
    	for(int eee = 1 ; eee <= T ; eee++) {
    		n = read() , m = read();
    		for(int i = 1 ; i <= n ; i++)
    			a[i] = read();
    		
    		Discretize(a + 1 , a + n + 1);
    		for(int i = 1 ; i <= m ; i++)
    			f[i].clear();
    		for(int i = 1 ; i <= n ; i++)  {
    			f[1].add(a[i] , 1);
    			for(int j = 2 ; j <= m ; j++) {
    					f[j].add(a[i] , f[j - 1].getsum(a[i] - 1));
    			}
    		}
    		int ans = 0;
    		printf("Case #%d: %d
    " , eee , f[m].getsum(n) % mod);
    	}
    	return 0;
    }
    

    D. 【例题4】区间修改区间查询

    题目

    思路

    (b)(a)的查分数组,则有:

    [sum^x_{i=1}a_i=sum^x_{i=1}sum^i_{j=1}b_i= egin{cases} b_1+\ b_1+b_2+\ b_1+b_2+b_3+\ cdots\ b_1+b_2+cdots+b_x end{cases} =sum^x_{i=1}(x-i+1)cdot b_i=sum^x_{i=1}ig( (x+1)b_i-icdot b_i ig)=(x+1)cdot sum^x_{i=1}b_i-sum^x_{i=1}icdot b_i ]

    用两个树状数组,一个维护(b_i),另一个维护(icdot b_i)即可

    (iin [l,r],a_i+=d),即:(b_l=b_l+dqquad b_{r+1}=b_{r+1}-d qquad ib_l=ib_l+lcdot dqquad ib_{r+1}=ib_{r+1}-(r+1)cdot d)

    查询操作见上

    代码

    #include <iostream>
    #include <cstdio>
    using namespace std;
    #define N 1000010
    #define ll long long
    int read() {
    	int re = 0;
    	char c = getchar();
    	bool sig = false;
    	while(c < '0' || c > '9') {
    		if(c == '-')	sig = true;
    		c = getchar();
    	}
    	while(c >= '0' && c <= '9')
    		re  = (re << 1) + (re << 3) + c - '0' , c = getchar();
    	return sig ? -re : re;
    }
    struct node {
    	int siz;
    	ll a[N * 2];
    	#define lowbit(_) ((_) & -(_))
    	void change(int i , ll dat) {
    		for( ; i <= siz ; i += lowbit(i))
    			a[i] += dat;
    	}
    	ll ask(int r) {
    		ll sum = 0;
    		for( ; r ; r -= lowbit(r))
    			sum += a[r];
    		return sum;
    	}
    }ib , b;
    
    int n , q;
    int a[N];
    void change(int l , int r , ll d) {
    	b.change(l , d);
    	b.change(r + 1 , -d);
    	ib.change(l , d * l);
    	ib.change(r + 1 , -d * (r + 1ll));
    }
    ll ask(int x) {
    	return (x + 1ll) * b.ask(x) - ib.ask(x);
    }
    signed main() {
    	n = read(); q = read();
    	b.siz = ib.siz = n;
    	for(int i = 1 ; i <= n ; i++) {
    		a[i] = read();
    		b.change(i , a[i] - a[i - 1]);
    		ib.change(i , 1ll * i * (a[i] - a[i - 1]));
    	}
    	for(int i = 1 ; i <= q ; i++) {
    		int ty = read() , l = read() , r = read();
    		if(ty == 1)
    			change(l , r , read());
    		else
    			printf("%lld
    " , ask(r) - ask(l - 1));
    		
    	}
    	return 0;
    }
    

    E. 【例题5】单点修改区间查询

    题目

    思路

    二维树状数组(见代码),没什么好说的

    代码

    #include <iostream>
    #include <cstdio>
    using namespace std;
    #define N 5020
    #define ll long long
    int read() {
    	int re = 0;
    	char c = getchar();
    	bool sig = false;
    	while(c < '0' || c > '9') {
    		if(c == '-')	sig = true;
    		c = getchar();
    	}
    	while(c >= '0' && c <= '9')
    		re  = (re << 1) + (re << 3) + c - '0' , c = getchar();
    	return sig ? -re : re;
    }
    int n , m;
    struct TreeArray {
    	ll a[N][N];
    	#define lowbit(_) ((_) & -(_))
    	void change(int x , int y , ll d) {
    		for(int i = x ; i <= n ; i += lowbit(i))
    			for(int j = y ; j <= m ; j += lowbit(j))
    				a[i][j] += d;
    	}
    	ll ask(int x , int y) {
    		ll sum = 0;
    		for(int i = x ; i; i -= lowbit(i))
    			for(int j = y ; j ; j -= lowbit(j))
    				sum += a[i][j];
    		return sum;
    	}
    }a;
    int main() {
    	n = read() , m = read();
    	while(true) {
    		int ty;
    		if(scanf("%d" , &ty) == EOF) return 0;
    		int lx = read() , ly = read();
    		if(ty == 1) {
    			ll d = read();
    			a.change(lx , ly , d);
    		}
    		else {
    			int rx = read() , ry = read();
    			printf("%lld
    " , a.ask(rx , ry) - a.ask(lx - 1 , ry) - a.ask(rx , ly - 1) + a.ask(lx - 1 , ly - 1));
    		}
    	}
    	return 0;
    }
    

    F. 【例题6】区间修改区间查询

    题目

    思路

    错解

    二维线段树:一个节点表示一块矩阵的面积(也可以是点),该矩阵又划分为四个子矩阵(左上,右上,左下,右下),作为它的子节点,然后按普通线段树做

    其实这样做的复杂度是不行的(应该是(O(max(n,m)cdot log (nm)cdot q))),同样,这样的线段树扩展到(k)维,单次线段树操作的复杂度是(O(n^{k-1}cdot log n)),具体原因要从线段树时间复杂度原理考虑,这里不做赘述.

    这样做的正确性是没问题的,就是超时了.

    算是吸取教训吧

    #include <iostream>
    #include <cstdio>
    #define ll long long
    //#pragma GCC optimize(2)
    using namespace std;
    int n , m;
    int read() {
    	int re = 0;
    	char c = getchar();
    	bool sig = false;
    	while(c < '0' || c > '9') {
    		if(c == '-')	sig = true;
    		c = getchar();
    	}
    	while(c >= '0' && c <= '9')
    		re  = (re << 1) + (re << 3) + c - '0' , c = getchar();
    	return sig ? -re : re;
    }
    #define N 2048 * 2048 * 4
    struct node {
    	
    	ll tag[N];
    	ll dat[N];
    	int Lx[N] , Rx[N] , Ly[N] , Ry[N];
    	int s1[N] , s2[N] , s3[N] , s4[N];
    	int root;
    	
    	#define size(_) ((Rx[_] - Lx[_] + 1) * (Ry[_] - Ly[_] + 1))
    	void spread(int p) {
    		tag[s1[p]] += tag[p] , dat[s1[p]] += tag[p] * size(s1[p]);
    		tag[s2[p]] += tag[p] , dat[s2[p]] += tag[p] * size(s2[p]);
    		tag[s3[p]] += tag[p] , dat[s3[p]] += tag[p] * size(s3[p]);
    		tag[s4[p]] += tag[p] , dat[s4[p]] += tag[p] * size(s4[p]);
    		tag[p] = 0;
    		dat[0] = tag[0] = 0;
    	}
    	int build(int lx , int rx , int ly , int ry) {
    		static int cnt = 0;
    		int p = ++cnt;
    		if(lx > rx || ly > ry)	return 0;
    		Lx[p] = lx , Rx[p] = rx , Ly[p] = ly , Ry[p] = ry;
    		int mx = (lx + rx) / 2 , my = (ly + ry) / 2;
    		if(!(lx == rx && ly == ry)) {
    			s1[p] = build(lx , mx , ly , my);
    			if(lx != rx)	s2[p] = build(mx + 1 , rx , ly , my);
    			if(ly != ry)	s3[p] = build(lx , mx , my + 1 , ry);
    			if(lx != rx && ly != ry)	s4[p] = build(mx + 1 , rx , my + 1 , ry);
    		}
    		return p;
    	}
    	void change(int lx , int rx , int ly , int ry , ll d , int p) {
    		if(p == 0)	return;
    		if(lx <= Lx[p] && rx >= Rx[p] && ly <= Ly[p] && ry >= Ry[p]) {
    			tag[p] += d , dat[p] += size(p) * d;
    			return;
    		}
    		if(lx > Rx[p] || rx < Lx[p] || ly > Ry[p] || ry < Ly[p])
    			return ;
    		spread(p);
    		change(lx , rx , ly , ry , d , s1[p]);
    		change(lx , rx , ly , ry , d , s2[p]);
    		change(lx , rx , ly , ry , d , s3[p]);
    		change(lx , rx , ly , ry , d , s4[p]);
    		dat[p] = dat[s1[p]] + dat[s2[p]] + dat[s3[p]] + dat[s4[p]];
    	}
    	ll ask(int lx , int rx , int ly , int ry , int p) {
    		if(p == 0)	return 0;
    		if(lx <= Lx[p] && rx >= Rx[p] && ly <= Ly[p] && ry >= Ry[p])
    			return dat[p];
    		if(lx > Rx[p] || rx < Lx[p] || ly > Ry[p] || ry < Ly[p])
    			return 0;
    			
    		spread(p);
    		return 
    		ask(lx , rx , ly , ry , s1[p]) +
    		ask(lx , rx , ly , ry , s2[p]) +
    		ask(lx , rx , ly , ry , s3[p]) +
    		ask(lx , rx , ly , ry , s4[p]);
    	}
    }SegTree;
    int main() {
    	n = read() , m = read();
    	SegTree.root = SegTree.build(1 , n , 1 , m);
    	while(true) {
    		int ty;
    		if(scanf("%d" , &ty) == EOF) return 0;
    		int lx = read() , ly = read() , rx = read() , ry = read();
    		if(ty == 1) {
    			SegTree.change(lx , rx , ly , ry , read() , SegTree.root);
    		}
    		else
    			printf("%lld
    " , SegTree.ask(lx , rx , ly , ry , SegTree.root));
    	}
    	return 0;
    }
    

    正解(未写)

    前两题的结合版,区间查改的二维树状数组


  • 相关阅读:
    Zookeeper 记录
    Eureka 分析记录
    Hbase 学习记录
    小技巧 Mongodb 动态查询 除去 _class 条件
    科技的继续发展会带给我们什么?
    无限拓展热区域缓存服务方案
    Git 的 4 个阶段的撤销更改
    自动化运维利器 Fabric
    Linux Shell 文本处理工具
    如何在 Linux 中找出 CPU 占用高的进程
  • 原文地址:https://www.cnblogs.com/dream1024/p/14729752.html
Copyright © 2011-2022 走看看