树状数组
树状数组
一种可以单点修改,查询前缀和的数据结构。
每次操作\(O(\log n)\)
void Modify(int x,int k) {
for(; x<=n; x+=x&(-x)) t[x]+=k;
}
int Query(int x) {
int res=0;
for(; x; x-=x&(-x)) res+=t[x];
return res;
}
可以使用差分树状数组。
也可以查询逆序对:建立值域树状数组,即每一次插入一个数\(a_i\),查询前缀和就是前面比他小的数,\(i-Query(a_i)\)即为比他大的数。
所有答案之和即为答案。
P1966 [NOIP2013 提高组] 火柴排队
有\(a\)数组和\(b\)数组存放火柴。
排序不等式:即让所有大小排名相等的或火柴互相对应。
\(a_i\)排名\(ra_i\),理应排到\(rb_i\)那里。
有q【ra_i的原排名】=rb_i的原排名。
计算q数组的逆序对即可。
q数组的意义是\(a_i\)应移动到\(q_{a_i}\).
#include<algorithm>
#include<cstdio>
#include<iostream>
using namespace std;
typedef long long ll;
const int MAXN=1e5,MOD=1e8-3;
int n,n1,n2,ra[MAXN+10],rb[MAXN+10],q[MAXN+10];
int c[MAXN+10];
ll ans;
struct node {
int id,val;
} a[MAXN+10],b[MAXN+10];
bool cmp(node x,node y) {
if(x.val==y.val) return x.id<y.id;
return x.val<y.val;
}
ll Query(int x) {
ll ans=0;
for(; x; x-=x&(-x)) ans+=c[x];
return ans;
}
void Modify(int x) {
for(; x<=n; x+=x&(-x)) c[x]+=1;
}
int main() {
scanf("%d",&n);
for(int i=1; i<=n; i++) {
scanf("%d",&a[i].val);
a[i].id=i;
}
for(int i=1; i<=n; i++) {
scanf("%d",&b[i].val);
b[i].id=i;
}
sort(a+1,a+1+n,cmp);
sort(b+1,b+1+n,cmp);
for(int i=1; i<=n; i++) q[a[i].id]=b[i].id;
for(int i=1; i<=n; i++) {
Modify(q[i]);
ans=ans+(i-Query(q[i]))%MOD;
}
printf("%lld\n",ans%MOD);
return 0;
}
P3605 [USACO17JAN]Promotion Counting P
求子树比父亲大的节点数。
dfs。
用回溯dfs时候的比他大的数减去没回溯时候的比他大的数。\(O(\log n)\)
每个数存进树状数组。
#include<algorithm>
#include<cstdio>
#include<iostream>
using namespace std;
const int MAXN=3e5;
int n,p[MAXN+10],rk[MAXN+10],ans[MAXN+10];
int head[MAXN+10],ver[MAXN+10],nxt[MAXN+10],tot;
int t[MAXN+10];
bool cmp(int x,int y) {
return p[x]<p[y];
}
void Addedge(int x,int y) {
ver[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
void Modify(int x,int k) {
for(; x<=n; x+=x&(-x)) t[x]+=k;
}
int Query(int x) {
int res=0;
for(; x; x-=x&(-x)) res+=t[x];
return res;
}
void dfs(int u) {
ans[u]-=Query(n)-Query(p[u]);
for(int i=head[u]; i; i=nxt[i]) dfs(ver[i]);
ans[u]+=Query(n)-Query(p[u]);
Modify(p[u],1);
}
int main() {
scanf("%d",&n);
for(int i=1; i<=n; i++) {
scanf("%d",&p[i]);
rk[i]=i;
}
sort(rk+1,rk+1+n,cmp);
for(int i=1; i<=n; i++) p[rk[i]]=i;
for(int i=2,f; i<=n; i++) {
scanf("%d",&f);
Addedge(f,i);
}
dfs(1);
for(int i=1; i<=n; i++) printf("%d\n",ans[i]);
return 0;
}
P1972 [SDOI2009]HH的项链
离线树状数组。
现将所有的询问按照右端点排序。
因为对所有同颜色的贝壳,只有最后边的有用,前面的就没用了。
对于一个贝壳颜色\(c\),删除前面那个同颜色的,在这个位置插入新的。此处用树状数组。
树状数组维护的是前缀中颜色不同的贝壳的个数。
#include<algorithm>
#include<cstdio>
#include<iostream>
using namespace std;
const int MAXN=1e6;
int n,m,a[MAXN+10],c[MAXN+10],ans[MAXN+10],prev[MAXN+10];
struct node {
int l,r,id;
} q[MAXN+10];
bool cmp(node x,node y) {
return x.r<y.r;
}
void Modify(int x,int k) {
for(; x<=n; x+=x&(-x)) c[x]+=k;
}
int Query(int x) {
int res=0;
for(; x; x-=x&(-x)) res+=c[x];
return res;
}
int main() {
scanf("%d",&n);
for(int i=1; i<=n; i++) scanf("%d",&a[i]);
scanf("%d",&m);
for(int i=1; i<=m; i++) {
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id=i;
}
sort(q+1,q+1+m,cmp);
for(int i=1,k=1; i<=m; i++) {
for(int j=k; j<=q[i].r; j++) {
if(prev[a[j]]) {
Modify(prev[a[j]],-1);
}
prev[a[j]]=j;
Modify(j,1);
}
k=q[i].r+1;
ans[q[i].id]=Query(q[i].r)-Query(q[i].l-1);
}
for(int i=1; i<=m; i++) printf("%d\n",ans[i]);
return 0;
}
P4113 [HEOI2012]采花
同上一题。
求一段区间出现超过2次的颜色个数。
储存前第二个同颜色的花的位置。
因为只有前第二个才有贡献。
包括前第二个的就超过两次了,不包括就只有1个或0个
#include<algorithm>
#include<cstdio>
#include<iostream>
using namespace std;
const int MAXN=2e6;
int n,c,m,a[MAXN+10],prev[2][MAXN+10],ans[MAXN+10];
int t[MAXN+10]; //prev[1]是前第二个
struct node {
int l,r,id;
} q[MAXN+10];
bool cmp(node x,node y) {
return x.r<y.r;
}
void Modify(int x,int k) {
for(; x<=n; x+=x&(-x)) t[x]+=k;
}
int Query(int x) {
int res=0;
for(; x; x-=x&(-x)) res+=t[x];
return res;
}
int main() {
scanf("%d%d%d",&n,&c,&m);
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);
q[i].id=i;
}
sort(q+1,q+1+m,cmp);
for(int i=1,k=1; i<=m; i++) {
for(int j=k; j<=q[i].r; j++) {
if(prev[0][a[j]]) {
if(prev[1][a[j]]) Modify(prev[1][a[j]],-1);
Modify(prev[0][a[j]],1);
}
prev[1][a[j]]=prev[0][a[j]];
prev[0][a[j]]=j;
}
k=q[i].r+1;
ans[q[i].id]=Query(q[i].r)-Query(q[i].l-1);
}
for(int i=1; i<=m; i++) printf("%d\n",ans[i]);
return 0;
}
P4054 [JSOI2009]计数问题
二维树状数组。
#include<algorithm>
#include<cstdio>
#include<iostream>
using namespace std;
const int MAXN=300,MAXC=100;
int n,m,q,t[MAXN+10][MAXN+10][MAXC+10],colr[MAXN+10][MAXN+10];
void Modify(int x,int y,int k,int c) {
for(int i=x; i<=n; i+=i&(-i))
for(int j=y; j<=m; j+=j&(-j))
t[i][j][c]+=k;
}
int Query(int x,int y,int c) {
int ans=0;
for(int i=x; i; i-=i&(-i))
for(int j=y; j; j-=j&(-j))
ans+=t[i][j][c];
return ans;
}
int main() {
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++)
for(int j=1,c; j<=m; j++) {
scanf("%d",&c);
colr[i][j]=c;
Modify(i,j,1,c);
}
scanf("%d",&q);
for(int i=1,opt,lx,rx,ly,ry,c; i<=q; i++) {
scanf("%d",&opt);
if(opt==1) {
scanf("%d%d%d",&lx,&ly,&c);
Modify(lx,ly,-1,colr[lx][ly]);
colr[lx][ly]=c;
Modify(lx,ly,1,c);
} else {
scanf("%d%d%d%d%d",&lx,&rx,&ly,&ry,&c);
printf("%d\n",Query(rx,ry,c)-Query(lx-1,ry,c)-Query(rx,ly-1,c)+Query(lx-1,ly-1,c));
}
}
return 0;
}
例题
一个由小写字母构成的字符串A,长度为len。如果有其中一个元素出现的次数大于\(\left\lfloor \frac{len}{2} \right\rfloor\),那么该元素就是数组A的主元素,显然数组A最多只有1个主元素,如果数组A有主元素,那么被称为“优美的”。
对于字符串B,求他的子串中“优美的”个数。
得知,“优美的”数组超过一半部分由一个字母构成。分类讨论每一个字母,加和即可得到答案。
对于原字符串,构建一个数组,若每个元素为所讨论的字母,设为1。否则,设为-1。求出前缀和sum数组。
对于子串元素s~t,若为优美的,即\(sum_{t}-sum_{s-1}>0\)。
所以\(sum_{t}>sum_{s-1}\),如同求解逆序对,运用树状数组求解个数即可。