BZOJ 4012: [HNOI2015]开店 【主席树区间修改+树剖维护路径和】

BZOJ 传送门
洛谷 传送门

题目分析:

dalao博客
把距离看成 d e p [ u ] + d e p [ v ] − 2 ∗ d e p [ l c a ] dep[u]+dep[v]-2*dep[lca] dep[u]+dep[v]2dep[lca]
d e p [ l c a ] dep[lca] dep[lca]的时候就用v点在路径上打标记,u点向上统计到根的路径和(似乎是lca的老套路?)
路径就用树链剖分+线段树维护
由于对v点有区间限制,所以把线段树换成主席树就好了。
主席树区间修改不用懒标记(那样会多出很多节点),因为这道题的边权是定的,所以只需要知道贡献的次数,直接存一下区间被整段覆盖的次数就好了。

主席树空间应该是nlog2n的,但是实测开n*50能过

Code:

#include<cstdio>
#include<cctype>
#include<algorithm>
#define LL long long
#define maxn 150005
#define maxp 150005*50//origin 150
using namespace std;
inline void read(int &a){
    char c;while(!isdigit(c=getchar()));
    for(a=c-'0';isdigit(c=getchar());a=a*10+c-'0');
}
int n,m,A,fa[maxn],son[maxn],siz[maxn],top[maxn],dfn[maxn],tt;
int fir[maxn],nxt[maxn<<1],to[maxn<<1],w[maxn<<1],cnt;
int rt[maxn],lc[maxp],rc[maxp],tim[maxp],tot;
LL sum[maxp],E[maxn],D[maxn],dep[maxn];
struct node{
    int x,id;
    bool operator < (const node &p)const{return x==p.x?id<p.id:x<p.x;}
}a[maxn];
inline void line(int x,int y,int z){nxt[++cnt]=fir[x];fir[x]=cnt;to[cnt]=y;w[cnt]=z;}
void dfs1(int u){
    siz[u]=1;
    for(int i=fir[u],v;i;i=nxt[i]) if((v=to[i])!=fa[u]){
        fa[v]=u,dep[v]=dep[u]+w[i];
        dfs1(v);siz[u]+=siz[v];
        if(siz[v]>siz[son[u]]) son[u]=v;
    }
}
void dfs2(int u,int tp){
    top[u]=tp,dfn[u]=++tt,E[tt]=dep[u]-dep[fa[u]];
    if(son[u]) dfs2(son[u],tp);
    for(int i=fir[u];i;i=nxt[i]) if(!dfn[to[i]]) dfs2(to[i],to[i]);
}
void insert(int &now,int l,int r,int x,int y){
    sum[++tot]=sum[now]+E[y]-E[x-1],lc[tot]=lc[now],rc[tot]=rc[now],tim[tot]=tim[now];
    now=tot;
    if(x==l&&r==y) {tim[now]++;return;}
    int mid=(l+r)>>1;//x,y要拆开,统计sum时需要
    if(y<=mid) insert(lc[now],l,mid,x,y);
    else if(x>mid) insert(rc[now],mid+1,r,x,y);
    else insert(lc[now],l,mid,x,mid),insert(rc[now],mid+1,r,mid+1,y);
}
LL query(int now,int p,int l,int r,int x,int y){
    if(!now) return 0;
    if(x==l&&r==y) return sum[now]-sum[p];
    LL ret=(E[y]-E[x-1])*(tim[now]-tim[p]);
    int mid=(l+r)>>1;
    if(y<=mid) return ret+query(lc[now],lc[p],l,mid,x,y);
    else if(x>mid) return ret+query(rc[now],rc[p],mid+1,r,x,y);
    else return ret+query(lc[now],lc[p],l,mid,x,mid)+query(rc[now],rc[p],mid+1,r,mid+1,y);
}
void modify(int &rt,int x){
    while(x) insert(rt,1,n,dfn[top[x]],dfn[x]),x=fa[top[x]];
}
LL solve(int l,int r,int x){
    LL ret=0;
    while(x) ret+=query(rt[r],rt[l],1,n,dfn[top[x]],dfn[x]),x=fa[top[x]];
    return ret;
}
int main()
{
    int x,y,z,l,r;LL ans=0;
    read(n),read(m),read(A);
    for(int i=1;i<=n;i++) read(a[i].x),a[i].id=i;
    sort(a+1,a+1+n);
    for(int i=1;i<n;i++) read(x),read(y),read(z),line(x,y,z),line(y,x,z);
    dfs1(1),dfs2(1,1);
    for(int i=1;i<=n;i++) E[i]+=E[i-1],D[i]=D[i-1]+dep[a[i].id];
    for(int i=1;i<=n;i++) modify(rt[i]=rt[i-1],a[i].id);
    while(m--){
        read(x),read(y),read(z);
        l=min((y+ans)%A,(z+ans)%A),r=max((y+ans)%A,(z+ans)%A);
        l=lower_bound(a+1,a+1+n,(node){l,0})-a,r=upper_bound(a+1,a+1+n,(node){r,n+1})-a-1;
        printf("%lld\n",ans=D[r]-D[l-1]+dep[x]*(r-l+1)-solve(l-1,r,x)*2);
    }
}

上面的insert和query还有另一种写法(不改x,y),对应了线段树中的两种写法,但是上面那种快一些(大概是因为查询跨线段树区间mid的很少)。

void insert(int &now,int l,int r,int x,int y){
	sum[++sz]=sum[now],tim[sz]=tim[now],lc[sz]=lc[now],rc[sz]=rc[now],now=sz;
	if(x<=l&&r<=y) {sum[now]+=E[r]-E[l-1],tim[now]++;return;}
	int mid=(l+r)>>1;
	if(x<=mid) insert(lc[now],l,mid,x,y);
	if(y>mid) insert(rc[now],mid+1,r,x,y);
	sum[now]=sum[lc[now]]+sum[rc[now]]+(E[r]-E[l-1])*tim[now];
}
LL query(int now,int p,int l,int r,int x,int y){
	if(!now) return 0;
	if(x<=l&&r<=y) return sum[now]-sum[p];
	int mid=(l+r)>>1;LL ret=(E[min(r,y)]-E[max(x,l)-1])*(tim[now]-tim[p]);//attention!
	if(x<=mid) ret+=query(lc[now],lc[p],l,mid,x,y);
	if(y>mid) ret+=query(rc[now],rc[p],mid+1,r,x,y);
	return ret;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值