如何实现数据结构的嵌套?
首先我们知道,单个数据结构是对一些存有某些信息的节点进行操作,从而达到目的。
然后我们将这些节点换成另一种数据结构,更改的时候对某些数据结构进行修改,就可以实现嵌套。
二维树状数组
其实是最好写的一种树套树。
单点修改,区间查询
就像上文说的一样,我们对每一行开一个树状数组,维护该列的信息,再使用一个树状数组来维护这些树状数组,单点修改时直接在大树状数组里修改小树状数组即可。
区间查询做一个差分就好了。
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;
}
标签:入门,limits,int,sum,times,数组,树套,dp,去世
From: https://www.cnblogs.com/luqyou/p/18086110