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

博客介绍LNOI2014 LCA加强版题目的做法。通过将边权赋值给to,利用主席树,从第i - 1颗线段树修改i到根路径标记得到第i颗。询问时用第r颗减第l - 1颗,利用树剖求标记×权值。还介绍了主席树区间修改的标记永久化思想。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

qwq直接介绍做法了

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

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

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

那么现在问题就转化成了

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

这里我们运用一个标记永久化的思想,就是在我们updateupdateupdate的时候,我们会修改沿途每一个经过的节点的sumsumsum(这里注意要维护),并且每走到一个被修改区间包含的区间,就修改一个addaddadd标记(+1),然后直接returnreturnreturn,不递归下去。

而询问的时候呢,我们会记录一下沿途的addaddadd标记之和,到了一个被包含的区间之后,就returnreturnreturn add∗getsum(l,r)+t[root].sumadd*getsum(l,r)+t[root].sumaddgetsum(l,r)+t[root].sum

这里getsumgetsumgetsum是一个边权转化成点权之后的前缀和

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

不过其实细节还是很多的

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

// luogu-judger-enable-o2
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<cstdlib>
#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<<x<<" "<<tmp<<endl;
   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<<l<<" "<<r<<" "<<x<<" "<<y<<endl;
   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<<newnum[top[x]]<<" "<<newnum[x]<<"***"<<" "<<x<<" "<<y<<endl;
     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<<newnum[x]<<" "<<newnum[y]<<"***"<<" "<<x<<" "<<y<<" "<<endl;
  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<<x<<" "<<y<<endl;
   }
   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<<i<<endl;
   }
   //cout<<"*****"<<endl;
   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<<l<<" "<<r<<endl;
     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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值