本篇文章将介绍一些非常规形态的树状数组的使用。
一)异或版
树状数组中记录的是一段值异或的结果。
例题:BZOJ2819
题目大意:
给定一棵树,每个节点是一堆石子,给定两种操作:
1.改变x号节点的石子数量
2.用从x到y的路径上的所有堆石子玩一次Nim游戏,询问是否有必胜策略
题解:
既然它只修改点的话,影响到的只是它这棵子树。那么很容易就想到了dfs序。这个子树就是连续一段。先维护每个点dfs开始时和结束时的时间戳。修改的时候先在它自己的开始、结束位置上xor它自己变成零,然后再修改。(x,y)路径上的xor值=query(x的开始) xor query(y的开始) xor lca(x,y)的点权。很好想通。LCA就倍增算一下好了。
代码如下:
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<math.h>
#define ll long long
#define inf 0x7f7f7f7f
#define N 500005
#define lb(x) (x&-x)
using namespace std;
ll read()
{
ll x=0,f=1;
char c=getchar();
while(c<'0' || c>'9') {if(c=='-') f=-1;c=getchar();}
while(c<='9' && c>='0') {x=x*10+c-'0';c=getchar();}
return x*f;
}
int n,m,x,y,v[N],e[N<<1],nex[N<<1],hd[N],tot,ind;
int t[N],l[N],r[N],fa[N][20],dep[N];
char op[5];
void mdy(int x,int v)
{
while(x<=n)
{
t[x]^=v;
x+=lb(x);
}
}
int query(int x)
{
int ret=0;
while(x)
{
ret^=t[x];
x-=lb(x);
}
return ret;
}
void add(int u,int v)
{
e[++tot]=v,nex[tot]=hd[u],hd[u]=tot;
e[++tot]=u,nex[tot]=hd[v],hd[v]=tot;
}
void dfs(int u)
{
l[u]=++ind;
for(int i=hd[u];i;i=nex[i])
{
if(e[i]==fa[u][0]) continue;
fa[e[i]][0]=u;
dep[e[i]]=dep[u]+1;
dfs(e[i]);
}
r[u]=ind;
}
void init()
{
for(int i=1;i<=19;i++)
for(int j=1;j<=n;j++) fa[j][i]=fa[fa[j][i-1]][i-1];
}
int lca(int u,int v)
{
if(dep[u]<dep[v]) swap(u,v);
int t=dep[u]-dep[v];
for(int i=0;i<=18;i++)
if((1<<i)&t) u=fa[u][i];
for(int i=18;i>=0;i--)
if(fa[u][i]!=fa[v][i]) u=fa[u][i],v=fa[v][i];
if(u==v) return u;
return fa[u][0];
}
int main()
{
n=read();
for(int i=1;i<=n;i++) v[i]=read();
for(int i=1;i<n;i++) add(read(),read());
dfs(1);init();
for(int i=1;i<=n;i++) mdy(l[i],v[i]),mdy(r[i]+1,v[i]);
m=read();
for(int i=1;i<=m;i++)
{
scanf("%s",op);
x=read(),y=read();
if(op[0]=='Q')
{
int t=lca(x,y);
int ret=query(l[x])^query(l[y])^v[t];
if(ret) puts("Yes");
else puts("No");
}
else
{
mdy(l[x],v[x]),mdy(r[x]+1,v[x]);
v[x]=y;
mdy(l[x],v[x]),mdy(r[x]+1,v[x]);
}
}
return 0;
}
二)MAX、MIN版
树状数组中记录的是一些值的最大值。
例题:BZOJ3594
题解:
令f[i][j]表示前i个数上升j次的最大LIS
那么有f[i][j]=max{f[k][l]|k < i,l <= j,a[k]+l <= a[i]+j}+1
由于dp方程记录的是最大值,因此树状数组也必须相匹配,记录最大值。
这也提示我们树状数组的变化是很灵活的,要根据需要决定。
代码如下:
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<iostream>
#include<algorithm>
#define lb(x) (x&(-x))
using namespace std;
int c[6005][505],dp[10005][505],a[10005];
int n,m,ans,mx;
void mdy(int x,int y,int z)
{
for(int i=x;i<=mx+m;i+=lb(i))
for(int j=y;j<=m+1;j+=lb(j)) c[i][j]=max(c[i][j],z);
}
int query(int x,int y)
{
int ret=0;
for(int i=x;i;i-=lb(i))
for(int j=y;j;j-=lb(j)) ret=max(ret,c[i][j]);
return ret;
}
int main()
{
scanf("%d%d",&n,&m);
ans=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
mx=max(mx,a[i]);
}
for(int i=1;i<=n;i++)
for(int j=m;j>=0;j--)
{
dp[i][j]=query(a[i]+j,j+1)+1;
ans=max(ans,dp[i][j]);
mdy(a[i]+j,j+1,dp[i][j]);
}
printf("%d\n",ans);
return 0;
}