【树状数组】树状数组の奇技淫巧专场

来源:互联网 发布:2017最新一手数据 编辑:程序博客网 时间:2024/05/16 07:57

本篇文章将介绍一些非常规形态的树状数组的使用。

一)异或版

树状数组中记录的是一段值异或的结果。
例题: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;  }  
0 0
原创粉丝点击