P4338 [ZJOI2018]历史(树剖)(暴力)

前言

有点懊恼的一个题…
并没有其他那些ZJOI那么毒瘤,看出了关键结论,但最后维护卡在log条虚边的伞兵性质上了。

解析

第一眼:感觉根本不可做啊。
冷静一下,既然它还变态的带修,一定是可以转化成比较形式化的东西的。
对每个节点考虑对答案贡献了多少次,设 s x s_x sx 表示 x 子树内权值之和, m x x mx_x mxx 表示 max ⁡ ( a x , max ⁡ u ∈ s o n x s u ) \max(a_x,\max_{u\in son_{x}}s_{u}) max(ax,maxusonxsu),那么答案的上界显然是 s x − 1 s_x-1 sx1,但前提是不同子树的权值必须交错出现,因此其实应该是 min ⁡ ( s x − 1 , 2 ( s x − m x x ) ) \min(s_x-1,2(s_x-mx_x)) min(sx1,2(sxmxx)),递归的想可以发现这个结论一直都是对的。写一发暴力得到了30分,说明结论没有问题。

考虑如何快速的维护修改。
一个节点 x 收到某个儿子 son “一家独大”的制约当且仅当 2 ∗ s s o n ≥ s x + 1 2*s_{son}\ge s_x+1 2ssonsx+1。显然至多存在一个这样的儿子,定义其为 h s o n x hson_x hsonx(特别的, h s o n x hson_x hsonx 可以为 x 自己), ( x , h s o n x ) (x,hson_x) (x,hsonx) 这样的边定义为实边,其他边定义为虚边。
考虑对一个节点 x + w,贡献发生改变的必然是一条返祖链。进一步,发现对于返祖链上的一条实边,其父亲端的贡献必然不会改变。
然后我觉的虚实边变来变去我就不会了。

接下来,通过和树剖类似的证明 ,可以得出任意一条返祖链上的虚边最多有 log ⁡ a i \log a_i logai 条。
所以暴力跳修改所有虚边的贡献即可。
实现上,树剖后开两棵线段树,一棵维护虚实边,一棵维护 s x s_x sx 即可。
时间复杂度 O ( n log ⁡ n ( log ⁡ n + log ⁡ ∑ a i ) ) O(n\log n(\log n+\log {\sum a_i})) O(nlogn(logn+logai))

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned ll
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define ok debug("OK\n")

inline ll read() {
  ll x(0),f(1);char c=getchar();
  while(!isdigit(c)) {if(c=='-') f=-1;c=getchar();}
  while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
  return x*f;
}
const int N=4e5+100;
const int mod=1e9+7;

bool mem1;

inline ll ksm(ll x,ll k){
  ll res(1);
  while(k){
    if(k&1) res=res*x%mod;
    x=x*x%mod;
    k>>=1;
  }
  return res;
}

int n,m;

struct seg{
#define mid ((l+r)>>1)
#define ls (k<<1)
#define rs (k<<1|1)
  ll sum[N<<2],laz[N<<2];
  inline void pushup(int k){
    sum[k]=sum[ls]+sum[rs];
  }
  inline void add(int k,int l,int r,ll w){
    sum[k]+=(r-l+1)*w;
    laz[k]+=w;
  }
  inline void pushdown(int k,int l,int r){
    ll o=laz[k];laz[k]=0;
    if(!o) return;
    add(ls,l,mid,o);
    add(rs,mid+1,r,o);
    return;
  }
  void change(int k,int l,int r,int x,int y,ll w){
    if(x<=l&&r<=y){
      add(k,l,r,w);return;
    }
    pushdown(k,l,r);
    if(x<=mid) change(ls,l,mid,x,y,w);
    if(y>mid) change(rs,mid+1,r,x,y,w);
    pushup(k);
  }
  int findpre(int k,int l,int r,int p){
    if(!k) exit(0);
    //debug("k=%d (%d %d) p=%d\n",k,l,r,p);
    if(!sum[k]) return 0;
    if(l==r) return l;
    if(p<=mid) return findpre(ls,l,mid,p);
    pushdown(k,l,r);
    int o=findpre(rs,mid+1,r,p);
    if(o) return o;
    else return findpre(ls,l,mid,p);
  }
  ll ask(int k,int l,int r,int p){
    if(l==r) return sum[k];
    pushdown(k,l,r);
    if(p<=mid) return ask(ls,l,mid,p);
    else return ask(rs,mid+1,r,p);
  }
}t1,t2;
//t1:s
//t2:tag

ll a[N],s[N],mx[N],ans;
int hson[N],siz[N],top[N],dep[N],fa[N],dfn[N],pos[N],tim;
bool tag[N];
vector<int>e[N];
void dfs1(int x,int f){
  siz[x]=1;
  dep[x]=dep[f]+1;
  fa[x]=f;
  
  s[x]=mx[x]=a[x];hson[x]=x;
  for(int to:e[x]){
    if(to==f) continue;
    dfs1(to,x);
    siz[x]+=siz[to];
    s[x]+=s[to];
    if(s[to]>mx[x]){
      hson[x]=to;
      mx[x]=s[to];
    }
  }
  ans+=min(s[x]-1,2*(s[x]-mx[x]));
  ll sh=hson[x]==x?a[x]:s[hson[x]];
  if(2*sh<s[x]+1) hson[x]=0;
  if(hson[x]&&hson[x]!=x) tag[hson[x]]=1;
  return;
}
void dfs2(int x,int tp){
  //debug("x=%d tp=%d\n",x,tp);
  top[x]=tp;
  dfn[++tim]=x;pos[x]=tim;
  int son=0;
  for(int to:e[x]){
    if(to==fa[x]) continue;
    if(siz[to]>siz[son]) son=to;
  }
  if(son) dfs2(son,tp);
  for(int to:e[x]){
    if(to==fa[x]||to==son) continue;
    dfs2(to,to);
  }
  return;
}
void init(){  
  dfs1(1,0);
  dfs2(1,1);
  for(int i=1;i<=n;i++){
    t1.change(1,1,n,pos[i],pos[i],s[i]);
    if(i>1&&!tag[i]) t2.change(1,1,n,pos[i],pos[i],1);
    //printf("i=%d hson=%d s=%lld tag=%d\n",i,hson[i],s[i],tag[i]);
  }
  return;
}
inline ll calc(ll s,ll mx){
  return min(s-1,2*(s-mx));
}

inline void upd(int x,int w){
  int ori=x;
  //printf("upd: x=%d w=%d\n",x,w);
  if(hson[x]!=x){
    int son=hson[x];
    ll sh=son?t1.ask(1,1,n,pos[son]):0,sx=t1.ask(1,1,n,pos[x]);    
    ans-=calc(sx,sh);
    ans+=calc(sx+w,max(sh,a[x]+w));
    //printf("  x=%d son=%d sh=%lld ans=%lld\n",x,son,sh,ans);
    if(son&&2*sh<(sx+w)+1){
      t2.change(1,1,n,pos[son],pos[son],1);
      hson[x]=0;
    }
    if(2*(a[x]+w)>(sx+w)+1){
      hson[x]=x;
    }
  }
  while(x){
    int p=t2.findpre(1,1,n,pos[x]);
    if(p==0||p<pos[top[x]]) x=fa[top[x]];
    else{
      x=dfn[p];
      int son=hson[fa[x]];
      ll sf=t1.ask(1,1,n,pos[fa[x]]),
	sh=son?son==fa[x]?a[fa[x]]:t1.ask(1,1,n,pos[son]):0,sx=t1.ask(1,1,n,pos[x]);      
      ans-=calc(sf,sh);
      ans+=calc(sf+w,max(sx+w,sh));
      //printf("  x=%d son=%d sf=%lld sh=%lld sx=%lld p=%d ans=%lld\n",x,son,sf,sh,sx,p,ans);
      if(son&&2*sh<(sf+w)+1){
	t2.change(1,1,n,pos[son],pos[son],1);
	hson[fa[x]]=0;
      }
      if(2*(sx+w)>=(sf+w)+1){
	t2.change(1,1,n,pos[x],pos[x],-1);
	hson[fa[x]]=x;
      }
      x=fa[x];
    }
  }
  x=ori;
  a[x]+=w;
  while(x){
    t1.change(1,1,n,pos[top[x]],pos[x],w);
    x=fa[top[x]];
  }
  return;
}

bool mem2;
signed main(){
#ifndef ONLINE_JUDGE
  freopen("a.in","r",stdin);
  freopen("a.out","w",stdout);
#endif
  n=read();m=read();
  for(int i=1;i<=n;i++) a[i]=read();
  for(int i=1;i<n;i++){
    int x=read(),y=read();
    e[x].push_back(y);
    e[y].push_back(x);
  }
  init();
  printf("%lld\n",ans);
  for(int i=1;i<=m;i++){
    int x=read(),w=read();
    upd(x,w);
    printf("%lld\n",ans);
  }
  return 0;
}
/*
*/
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值