bzoj4448 [Scoi2015]情报传递 树链剖分+树状数组

题目大意:
维护一颗树,支持一下操作(可以离线)
1、给一个点赋值;
2、查询一条链上比有多少个点以及某个数(i-c)小的点有多少个。

(我这个题目大意略精简了一点哈=。=,但是还是可以比较轻松的把原题目转化成这个意思的)

题目分析:
我们可以把所有的操作离线出来,把赋值操作按照赋值的大小排序(相当于没排,不知道我这个沙茶为毛要排一下>_<)
然后把所有的询问按照要(i-c)从小到大排序。
树链剖分一下,用树状数组维护dfs序。
扫一遍所有的询问,把小于询问权值的赋值操作的位置在dfs序中改为1。
然后查询树链上1的个数就可以了。

时间复杂度O(mlog^2n)

代码如下:

#include <cstdio>
#include <algorithm>
#include <iostream>
#define N 220000
using namespace std;
inline int lowbit(int x){ return x&-x; }
int n,m,root,sum,cnt;
int fir[N],nes[N],v[N],tot=1;
int fa[N],dep[N],son[N],sz[N],pos[N],ld[N],top;
int fw[N],ans[N],_ans[N];
bool mark[N];
struct Change{
    int x,id;
    bool operator < (const Change &c) const  { return id<c.id; }
}a[N];
struct Query{
    int x,y,c,id;
    bool operator < (const Query &a) const { return c<a.c; }
}q[N];
void edge(int x,int y)
{
    v[++tot]=y;
    nes[tot]=fir[x];
    fir[x]=tot;
}
void update(int x)
{
    for(;x<=n;x+=lowbit(x))
        fw[x]++;
}
int query(int l,int r)
{
    int ans=0;
    for(;r;r-=lowbit(r))
        ans+=fw[r];
    for(l--;l;l-=lowbit(l))
        ans-=fw[l];
    return ans;
}
void dfs1(int c)
{
    dep[c]=dep[fa[c]]+1;
    sz[c]=1; son[c]=0;
    for(int t=fir[c];t;t=nes[t])
    {
        dfs1(v[t]);
        sz[c]+=sz[v[t]];
        if(sz[v[t]]>sz[son[c]]) son[c]=v[t];
    }
}
void dfs2(int c)
{
    pos[c]=++top;
    ld[c]=c;
    if(son[fa[c]]==c) ld[c]=ld[fa[c]];
    if(son[c]) dfs2(son[c]);
    for(int t=fir[c];t;t=nes[t])
    {
        if(v[t]==son[c]) continue;
        dfs2(v[t]);
    }
}
void solve(int c)
{
    int x=q[c].x,y=q[c].y;
    int id=q[c].id;
    while(ld[x]!=ld[y])
    {
        if(dep[ld[x]]<dep[ld[y]]) swap(x,y);
        ans[id]+=pos[x]-pos[ld[x]]+1;
        _ans[id]+=query(pos[ld[x]],pos[x]);
        x=fa[ld[x]];
    }
    if(dep[x]<dep[y]) swap(x,y);
    ans[id]+=pos[x]-pos[y]+1;
    _ans[id]+=query(pos[y],pos[x]);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&fa[i]);
        if(fa[i]==0) root=i;
        else edge(fa[i],i);
    }
    dfs1(root); dfs2(root);
    scanf("%d",&m);
    for(int i=1,opt,x,y,z;i<=m;i++)
    {
        scanf("%d",&opt);
        if(opt==1)
        {
            cnt++;
            scanf("%d%d%d",&q[cnt].x,&q[cnt].y,&q[cnt].c);
            q[cnt].id=cnt;
            q[cnt].c=i-q[cnt].c;
        }
        else
        {
            scanf("%d",&x);
            if(!mark[x])
            {
                sum++;
                if(!mark[x]) mark[x]=true;
                a[sum].x=x; a[sum].id=i;
            }
        }
    }
    sort(a+1,a+sum+1);
    sort(q+1,q+cnt+1);
    int now=1;
    for(int i=1;i<=cnt;i++)
    {
        while(now<=sum && a[now].id<q[i].c) update(pos[a[now].x]),now++;
        solve(i);
    }
    for(int i=1;i<=cnt;i++) printf("%d %d\n",ans[i],_ans[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值