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

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

一)异或版

树状数组中记录的是一段值异或的结果。
例题: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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值