bzoj3730 震波 [动态点分治,树状数组]

传送门


思路

如果没有强制在线的话可以离线之后CDQ分治随便搞。

有了强制在线之后……可能可以二维线段树?然而我不会算空间。

然后我们莫名其妙地想到了动态点分治,然后这题就差不多做完了。

点分树有一个重要的性质:若\(x,y\)在点分树上的\(lca\)\(w\),那么在原树上\(w\)也在\(x\rightarrow y\)的路径上。

我们可以在点分树上枚举\(lca\),算一下这个\(lca\)统领的连通块对\(x\)有多少贡献,减去\(x\)所在的连通块贡献,就ok了。

贡献可以用树状数组或动态开点线段树维护。普通树状数组的空间复杂度\(O(n^2)\),但每一个\(x\)开的树状数组可以只开到\(size_x\)的大小,复杂度就降为\(O(n\log n)\)了。

还有一题“烁烁的游戏”和这题差不多,但那题支持离线,也许有其他做法。


代码

辣鸡bzoj没有c++11差评QwQ

#include<bits/stdc++.h>
namespace my_std{
    using namespace std;
    #define pii pair<int,int>
    #define fir first
    #define sec second
    #define MP make_pair
    #define rep(i,x,y) for (int i=(x);i<=(y);i++)
    #define drep(i,x,y) for (int i=(x);i>=(y);i--)
    #define go(x) for (int i=head[x];i;i=edge[i].nxt)
    #define sz 101001
    typedef long long ll;
    typedef double db;
//  mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
//  template<typename T>inline T rnd(T l,T r) {return uniform_int_distribution<T>(l,r)(rng);}
//  template<typename T>inline void read(T& t)
//  {
//      t=0;char f=0,ch=getchar();double d=0.1;
//      while(ch>'9'||ch<'0') f|=(ch=='-'),ch=getchar();
//      while(ch<='9'&&ch>='0') t=t*10+ch-48,ch=getchar();
//      if(ch=='.'){ch=getchar();while(ch<='9'&&ch>='0') t+=d*(ch^48),d*=0.1,ch=getchar();}
//      t=(f?-t:t);
//  }
//  template<typename T,typename... Args>inline void read(T& t,Args&... args){read(t); read(args...);}
    template<typename T>inline bool chkmax(T &x,T y){return x<y?x=y,1:0;}
    template<typename T>inline bool chkmin(T &x,T y){return x>y?x=y,1:0;}
//  void file()
//  {
//      #ifndef ONLINE_JUDGE
//      freopen("a.txt","r",stdin);
//      #endif
//  }
//  #ifdef mod
//  ll ksm(ll x,int y){ll ret=1;for (;y;y>>=1,x=x*x%mod) if (y&1) ret=ret*x%mod;return ret;}
//  ll inv(ll x){return ksm(x,mod-2);}
//  #else
//  ll ksm(ll x,int y){ll ret=1;for (;y;y>>=1,x=x*x) if (y&1) ret=ret*x;return ret;}
//  #endif
//  inline ll mul(ll a,ll b){ll d=(ll)(a*(double)b/mod+0.5);ll ret=a*b-d*mod;if (ret<0) ret+=mod;return ret;}
}
using namespace my_std;

int n,m;
int a[sz],w[sz];
struct hh{int t,nxt;}edge[sz<<1];
int head[sz],ecnt;
void make_edge(int f,int t)
{
    edge[++ecnt]=(hh){t,head[f]};
    head[f]=ecnt;
    edge[++ecnt]=(hh){f,head[t]};
    head[t]=ecnt;
}

int sum,size[sz],root,mn;
bool vis[sz];
#define v edge[i].t
void findroot(int x,int fa)
{
    size[x]=1;
    int S=-1;
    go(x) if (v!=fa&&!vis[v])
    {
        findroot(v,x);
        size[x]+=size[v];
        chkmax(S,size[v]);
    }
    chkmax(S,sum-size[x]);
    if (chkmin(mn,S)) root=x;
}
int dep[sz]; // x在点分树上的深度
int fa[sz][30]; // x在点分树上到根的链上dep=i的祖先(特别地,fa[x][dep[x]]=x)
int dis[sz][30]; // x到fa[x][i]的距离(特别地,dis[x][dep[x]]=0)
vector<int>tr1[sz],tr2[sz];
// tr1[x]:x所统领的连通块里以到x的距离为关键字的树状数组
// tr2[x]:x所统领的连通块里以到fa[x][1]的距离为关键字的树状数组
void calc(int x,int f,int rt,int d)
{
    ++dep[x];fa[x][dep[x]]=rt;dis[x][dep[x]]=d;
    go(x) if (v!=f&&!vis[v]) calc(v,x,rt,d+1);
}
void build(int x)
{
    vis[x]=1;
    int all=sum;tr1[x].resize(all+5);
    calc(x,0,x,0);
    go(x) if (!vis[v])
    {
        mn=1e9;
        sum=size[v];if (sum>size[x]) sum=all-size[x];
        findroot(v,0);
        tr2[root].resize(sum+5);
        build(root);
    }
}
#undef v
void add1(int x,int p,int w){++p;while (p<(int)tr1[x].size()) tr1[x][p]+=w,p+=(p&(-p));}
int query1(int x,int p)
{
    ++p;
    if (p<=0) return 0;
    chkmin(p,(int)tr1[x].size()-1);
    int ret=0;
    while (p) ret+=tr1[x][p],p-=(p&(-p));
    return ret;
}
void add2(int x,int p,int w){++p;while (p<(int)tr2[x].size()) tr2[x][p]+=w,p+=(p&(-p));}
int query2(int x,int p)
{
    ++p;
    if (p<=0) return 0;
    chkmin(p,(int)tr2[x].size()-1);
    int ret=0;
    while (p) ret+=tr2[x][p],p-=(p&(-p));
    return ret;
}
void add(int x,int w)
{
    ::w[x]+=w;
    drep(i,dep[x],2)
        add1(fa[x][i],dis[x][i],w),
        add2(fa[x][i],dis[x][i-1],w);
    add1(fa[x][1],dis[x][1],w);
}
int query(int x,int d)
{
    int ret=query1(x,d);
    drep(i,dep[x]-1,1) ret+=query1(fa[x][i],d-dis[x][i])-query2(fa[x][i+1],d-dis[x][i]);
    return ret;
}

int main()
{
//  file();
    scanf("%d %d",&n,&m);
    rep(i,1,n) scanf("%d",&a[i]);
    int x,y,z;
    rep(i,1,n-1) scanf("%d %d",&x,&y),make_edge(x,y);
    sum=n;mn=1e9;findroot(1,0);
    build(root);
    rep(i,1,n) add(i,a[i]);
    int lastans=0;
    while (m--)
    {
        scanf("%d %d %d",&z,&x,&y);x^=lastans;y^=lastans;
        if (z==0) printf("%d\n",lastans=query(x,y));
        else add(x,y-w[x]);
    }
    return 0;
}

转载于:https://www.cnblogs.com/p-b-p-b/p/10464438.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值