洛谷3241 [HNOI2015]开店(标记永久化+主席树+树剖)

这个题可以说是LNOI2014 LCA这个题的加强版
所以那个题的博客就咕咕咕啦
hhh

qwq直接介绍做法了

考虑一个如何求两个点 x , y x,y x,y l c a lca lca到根的路径和,我们发现,如果我们在确定一个根后,将每一条边的边权赋值给他的 t o to to,那么实际上就是对于 x x x,把它所有到根的路径上所有点都标记 + 1 +1 +1,然后在 y y y点统计 y y y到根的路径上所有点的 标 记 数 × 权 值 标记数\times 权值 ×

那么我们会想到一个做法,考虑主席树,对于第 i i i颗线段树来说,他可以从第 i − 1 i-1 i1颗线段树的基础上,修改 i i i到根的路径的标记得到。

那么我们对于一次询问的话,我们可以用第 r r r颗线段树减去第 l − 1 l-1 l1颗线段树,得到一个新的线段树,然后直接对这个东西利用树剖,求出 u u u到根的路径上的 标 记 × 权 值 标记\times 权值 ×,而这里具体实现的话,我们可以直接维护区间和(相当于修改标记也是修改区间和),然后计算的时候,为了方便,我们可以分别在两棵树上进行计算答案,然后做减法。

那么现在问题就转化成了

我们该如何实现一颗主席树的区间修改呢?

这里我们运用一个标记永久化的思想,就是在我们 u p d a t e update update的时候,我们会修改沿途每一个经过的节点的 s u m sum sum(这里注意要维护),并且每走到一个被修改区间包含的区间,就修改一个 a d d add add标记(+1),然后直接 r e t u r n return return,不递归下去。

而询问的时候呢,我们会记录一下沿途的 a d d add add标记之和,到了一个被包含的区间之后,就 r e t u r n return return a d d ∗ g e t s u m ( l , r ) + t [ r o o t ] . s u m add*getsum(l,r)+t[root].sum addgetsum(l,r)+t[root].sum

这里 g e t s u m getsum getsum是一个边权转化成点权之后的前缀和

这样就可以实现区间修改了

不过其实细节还是很多的

具体的细节就直接看代码吧

// luogu-judger-enable-o2
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define pb push_back
#define mk make_pair
#define ll long long
#define lson ch[x][0]
#define rson ch[x][1]

using namespace std;

inline int read()
{
   int x=0,f=1;char ch=getchar();
   while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
   while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
   return x*f;
}

const int maxn = 2e5+1e2;
const int maxm = 2*maxn;

int point[maxn],nxt[maxm],to[maxm];
ll cost[maxm];
int cnt,n,m;
struct Node{
  int l,r;
  ll sum,add;
};
Node t[maxn*100];
ll sum[maxn];
int top[maxn],newnum[maxn],size[maxn],deep[maxn],fa[maxn],son[maxn];
int tmp,tot;
ll val[maxn];
ll mx;
int x[maxn];
ll newval[maxn];
int root[maxn];
int q;
ll dis[maxn],dsum[maxn];

ll getsum(int l,int r)
{
   return sum[r]-sum[l-1];
}

struct pp
{
    int num,val;
};
pp a[maxn];

void addedge(int x,int y,int w)
{
  nxt[++cnt]=point[x];
  to[cnt]=y;
  cost[cnt]=w;
  point[x]=cnt;
}

void dfs1(int x,int faa,int dep)
{
  size[x]=1;
  deep[x]=dep;
  int maxson = -1;
  for (int i=point[x];i;i=nxt[i])
  {
    int p = to[i];
    if (p==faa) continue;
    dis[p]=dis[x]+cost[i];
    dfs1(p,x,dep+1);
    val[p]=cost[i];
    size[x]+=size[p];
    fa[p]=x;
    if (maxson<size[p])
    {
      maxson=size[p];
      son[x]=p;
    }
  }
}

void dfs2(int x,int chain)
{
   top[x]=chain;
   newnum[x]=++tmp;
   newval[tmp]=val[x];
   //cout<
   if (!son[x]) return;
   dfs2(son[x],chain);
   for (int i=point[x];i;i=nxt[i])
   {
     int p = to[i];
     if (!newnum[p]) dfs2(p,p);
   }
}

int build(int l,int r)
{
   int now = ++tot;
   if (l==r) return now;
   int mid = l+r >> 1;
   t[now].l=build(l,mid);
   t[now].r=build(mid+1,r);
   return now;
}

bool cmp(pp a,pp b)
{
   return a.val<b.val;
}

int modify(int pre,int l,int r,int x,int y)
{
  // cout<
   int now = ++tot;
   t[now]=t[pre]; 
   if (x<=l && r<=y)
   {
     t[now].add++;
     return now;
   }
   int mid = l+r >> 1;
   t[now].sum+=max(getsum(max(x,l),min(r,y)),0ll);
   if (x<=mid) t[now].l=modify(t[pre].l,l,mid,x,y);
   if (y>mid) t[now].r=modify(t[pre].r,mid+1,r,x,y);
   return now; 
}

ll query(int root,int l,int r,int x,int y,ll add)
{
   if (x<=l && r<=y) return (add+t[root].add)*getsum(l,r)+t[root].sum;
   int mid = l+r >> 1;
   ll ans=0;
   if (x<=mid) ans=ans+query(t[root].l,l,mid,x,y,add+t[root].add);
   if (y>mid) ans=ans+query(t[root].r,mid+1,r,x,y,add+t[root].add);
   return ans;
}

void treeadd(int now,int x,int y)
{
  while (top[x]!=top[y])
  {
     if (deep[top[x]]<deep[top[y]]) swap(x,y);
     //cout<
     root[now]=modify(root[now],1,n,newnum[top[x]],newnum[x]);
     x=fa[top[x]];
  }
  if (deep[x]>deep[y]) swap(x,y);
  //cout<
  root[now]=modify(root[now],1,n,newnum[x],newnum[y]);
}

ll treesum(int now,int x,int y)
{
  ll ans=0;
  while (top[x]!=top[y])
  {
    if (deep[top[x]]<deep[top[y]]) swap(x,y);
    ans=ans+query(root[now],1,n,newnum[top[x]],newnum[x],0);
    x=fa[top[x]];
  }
  if (deep[x]>deep[y]) swap(x,y);
  ans=ans+query(root[now],1,n,newnum[x],newnum[y],0);
  return ans;
}

int main()
{
   n=read();q=read();mx=read();
   for (int i=1;i<=n;i++) a[i].val=read(),a[i].num=i,x[i]=a[i].val;
   sort(x+1,x+1+n);
   sort(a+1,a+1+n,cmp);
   for (int i=1;i<n;i++)
   {
      int x=read(),y=read(),w=read();
      addedge(x,y,w);
      addedge(y,x,w);
      //cout<
   }
   dfs1(1,0,0);
   dfs2(1,1);
   root[0]=build(1,n);
   //return 0;
   for (int i=1;i<=n;i++) sum[i]=sum[i-1]+newval[i];
   for (int i=1;i<=n;i++) dsum[i]=dsum[i-1]+dis[a[i].num];
   for (int i=1;i<=n;i++)
   {
      root[i]=root[i-1];
      treeadd(i,1,a[i].num);
      //cout<
   }
   //cout<<"*****"<
   ll lastans=0;
   for (int i=1;i<=q;i++)
   {
     ll u=read(),a=read(),b=read();
     ll l=min((a+lastans)%mx,(b+lastans)%mx);
     ll r=max((a+lastans)%mx,(b+lastans)%mx);
     l=lower_bound(x+1,x+1+n,l)-x;
     r=upper_bound(x+1,x+1+n,r)-x-1;
    // cout<
     ll f1 = treesum(l-1,1,u);
     ll f2 = treesum(r,1,u);
     ll len = r-l+1;
     lastans=dis[u]*len + dsum[r]-dsum[l-1]-2*(f2-f1);
     cout<<lastans<<"\n";
   } 
   return 0;
}

你可能感兴趣的:(主席树,树链剖分,标记永久化,c++)