树套树从入门到去世

如何实现数据结构的嵌套?

首先我们知道,单个数据结构是对一些存有某些信息的节点进行操作,从而达到目的。

然后我们将这些节点换成另一种数据结构,更改的时候对某些数据结构进行修改,就可以实现嵌套。

二维树状数组

其实是最好写的一种树套树。

单点修改,区间查询

就像上文说的一样,我们对每一行开一个树状数组,维护该列的信息,再使用一个树状数组来维护这些树状数组,单点修改时直接在大树状数组里修改小树状数组即可。

区间查询做一个差分就好了。

int lb(int x){
	return x&(-x);
}
void update(int x,int y,int k){
	for(int i=x;i<=n;i+=lb(i)){
		for(int j=y;j<=m;j+=lb(j)){
			c[i][j]+=k;
		}
	}
}
int query(int x,int y){
	int res=0;
	for(int i=x;i;i-=lb(i)){
		for(int j=y;j;j-=lb(j)){
			res+=c[i][j];
		}
	}
	return res;
} 
void work(int x,int y,int x_,int y_,int k){
	update(x,y,k),update(x,y_+1,-k),update(x_+1,y,-k),update(x_+1,y_+1,k);
}
int getans(int x,int y,int x_,int y_){
	return query(x_,y_)-query(x_,y-1)-query(x-1,y_)+query(x-1,y-1);
}

区间修改,单点查询

想一下我们利用树状数组维护一维序列时的区间修改,单点查询时怎么做?

直接维护原序列的差分序列,然后就可以转换为单点修改,区间查询了。

二维信息也是如此。注意容斥即可。

int lb(int x){
	return x&(-x);
}
void update(int x,int y,int k){
	for(int i=x;i<=n;i+=lb(i)){
		for(int j=y;j<=m;j+=lb(j)){
			c[i][j]+=k;
		}
	}
}
int query(int x,int y){
	int res=0;
	for(int i=x;i;i-=lb(i)){
		for(int j=y;j;j-=lb(j)){
			res+=c[i][j];
		}
	}
	return res;
} 
void work(int x,int y,int x_,int y_,int k){
	update(x,y,k),update(x,y_+1,-k),update(x_+1,y,-k),update(x_+1,y_+1,k);
}
int getans(int x,int y,int x_,int y_){
	return query(x_,y_)-query(x_,y-1)-query(x-1,y_)+query(x-1,y-1);
}

区间修改,区间查询 / 上帝造题的七分钟

还是考虑维护差分数组。

考虑二维差分数组 \(b\) 的定义,不难得到左上角为 \((1,1)\),右下角为 \((x,y)\) 的矩阵的和为 \(\sum\limits_{i=1}\limits^{x}\sum\limits_{j=1}\limits^{y}\sum\limits_{k=1}\limits^{i}\sum\limits_{l=1}\limits^{j} b_{k,l}.\)

于是我们集中注意力,发现每个 \(b_{i,j}\) 的出现次数仍然有规律(\(b_{i,j}\) 出现了 \((x-i+1)\times(y-j+1)\) 次),于是有 \(\sum\limits_{i=1}\limits^{x}\sum\limits_{j=1}\limits^{y}\sum\limits_{k=1}\limits^{i}\sum\limits_{l=1}\limits^{j} b_{k,l}=\sum\limits_{i=1}\limits^{x}\sum\limits_{j=1}\limits^y b_{i,j} \times (x-i+1) \times (y-j+1).\)

括号乘开,得:

\(\sum\limits_{i=1}\limits^{x}\sum\limits_{j=1}\limits^y b_{i,j} \times (x-i+1) \times (y-j+1) = b_{i,j} \times(xy+x+y+1) - b_{i,j} \times i \times (y+1) - b_{i,j} \times j \times (x+1) + b_{i,j} \times i \times j.\)

于是开四个树状数组,分别维护 \(b_{i,j}\)\(b_{i,j} \times i\)\(b_{i,j} \times j\)\(b_{i,j} \times i \times j\) 即可。

int lb(int x){
	return x&(-x);
}
void update(int x,int y,int k){
	for(int i=x;i<=n;i+=lb(i)){
		for(int j=y;j<=m;j+=lb(j)){
    		c1[i][j]+=k;
    		c2[i][j]+=k*x;
    		c3[i][j]+=k*y;
    		c4[i][j]+=k*x*y;
		}
	}
}
int query(int x,int y){
	int res=0;
	for(int i=x;i;i-=lb(i)){
		for(int j=y;j;j-=lb(j)){
			res+=(x+1)*(y+1)*c1[i][j]-(x+1)*c3[i][j]-(y+1)*c2[i][j]+c4[i][j];
		}
	}
	return res;
} 
void work(int x,int y,int x_,int y_,int k){
	update(x,y,k),update(x,y_+1,-k),update(x_+1,y,-k),update(x_+1,y_+1,k);
}
int getans(int x,int y,int x_,int y_){
	return query(x_,y_)-query(x_,y-1)-query(x-1,y_)+query(x-1,y-1);
}

Back From Summer '17 P6: Crowded Cities

乍一看,是一个三维偏序,可以使用 CDQ 分治解决。但是注意到每一维都只有 \(5000\),于是就给了我们使用二维树状数组的机会。

首先我们按 \(H\) 从小到大排序,就可以消掉一维。令 \(dp_i\) 为以 \(i\) 结尾的容纳人数最大值,那么 \(dp_i = \max\limits_{L_j \le L_i,W_j \le W_i} dp_j + P_i\)

我们把 \(dp_i\) 丢到点 \((L_i,W_i)\) 上,这样每次寻找 \(\max\limits_{L_j \le L_i,W_j \le W_i} dp_j\) 的过程相当于在树状数组上查询一个前缀的最大值,直接转移即可。

注意这题还卡空间,树状数组是不可以开 long long 的。这很好解决,用它维护下标,而非值即可。同时这样写的话输出方案也会简单很多。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxv=5000+10,V=5500,maxn=1e5+10;
ll n,dp[maxn],ans,lst[maxn],ansi;
int c[maxv][maxv];
struct building{
	ll a,b,c,p,id;
	bool operator <(building _){
		if(c==_.c){
			if(b==_.b){
				return a<_.a;
			}
			return b<_.b;
		}
		return c<_.c;
	}
}x[maxn];
int lb(int x){
	return x&(-x);
}
void update(int x,int y,int pos){
	for(int i=x;i<=V;i+=lb(i)){
		for(int j=y;j<=V;j+=lb(j)){
			if(dp[pos]>dp[c[i][j]]){
				c[i][j]=pos;
			}
		}
	}
}
int query(int x,int y){
	pair<ll,int> res;
	res={0,0};
	for(int i=x;i;i-=lb(i)){
		for(int j=y;j;j-=lb(j)){
			if(dp[c[i][j]]>res.first){
				res={dp[c[i][j]],c[i][j]};
			}
		}
	}
	return res.second;
}
signed main(){
//	freopen("a.txt","r",stdin);
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>x[i].a>>x[i].b>>x[i].c>>x[i].p;
		if(x[i].a<x[i].b) swap(x[i].a,x[i].b);
		x[i].id=i;
	}
	sort(x+1,x+n+1);
	for(int i=1;i<=n;i++){
		int maxi=query(x[i].a,x[i].b);
		dp[i]=dp[maxi]+x[i].p;
		lst[i]=maxi;
		if(ans<dp[i]) ans=dp[i],ansi=i;
		update(x[i].a,x[i].b,i);
	}
	ll tmp=ansi,cnt=0;
	cout<<ans<<endl;
	while(tmp){
		cnt++;
		tmp=lst[tmp];
	}
	cout<<cnt<<endl;
	while(ansi){
		cout<<x[ansi].id<<" ";
		ansi=lst[ansi];
	}
	return 0;
}
posted @ 2024-03-20 21:12  luqyou  阅读(19)  评论(0编辑  收藏  举报