zzuli 2520: 大小接近的点对 //(主席树||树状数组)+离线

2520: 大小接近的点对

时间限制: 1 Sec  内存限制: 256 MB
提交: 173  解决: 25
[提交] [状态] [讨论版] [命题人:外部导入]

题目描述

一天,Chika 对大小接近的点对产生了兴趣,她想搞明白这个问题的树上版本,你能帮助她吗?Chika 会给 你一棵有根树,这棵树有 n 个结点,被编号为 1 n,1 号结点是根。每个点有一个权值,i 号结点的权值为 a[i]。如果 u 是 v 的祖先结点,并且 abs(a[u]−a[v]) ≤K,那么 (u,v) 被称作一个“** 大小接近的点对 **”。 对于树上的每个结点 i,你都需要计算以其为根的子树中的“大小接近的点对”的数量。你需要知道: 
(1) abs(x) 代表 x 的绝对值。 
(2) 每个结点都是其自身的祖先结点.

 

 

输入

输入文件的第一行包含两个整数 n (1≤n≤105) 和 k (1≤k≤109),代表树中结点总数, 以及“大小接近的点对”的大小之差的上界。  
第二行包含 n 个整数,第 i 个整数是 a[i] (1≤ a[i] ≤109),代表 i 号结点的权值。 
第三行包含 n−1 个整数,第 i 个整数是 i+1 号结点的父结点。
 

输出

输出应该包含n行,每一行包括一个整数。第i行的整数代表以i为根的子树中的“大小接近的点对”的数量。

样例输入 Copy

7 5
2 4 4 1 4 6 4
1 2 3 1 2 3

样例输出 Copy

19
11
5
1
1
1
1

1.cout<<endl;我哭了

2.upperbound,lowerbound用清楚

眼前一片黑摸黑的感觉真难受

 

主席树,保存树之前状态,对于当前需要询问的树编号,(当前树的编号-1)and(先序遍历的序号+子树节点数量-1)

离散化的:

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int max_n =1e5+5,logn=40;
vector<int>g[max_n];
vector<int>v;
int a[max_n];
int root[max_n*logn],ls[max_n*logn],rs[max_n*logn];
int sum[max_n*logn];
int cnt;
int book[max_n];//重dfs编号
int p[max_n];//第i颗树的编号
int cson[max_n];//子树数量
LL sumabs[max_n]; //满足的对数
LL ans[max_n];
int getindex(int x) {
    return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
 
void build(int l,int r,int &now){
    now=++cnt;
    sum[now]=0;
    if(l==r) return;
    int mid=(l+r)>>1;
    build(l,mid,ls[now]);
    build(mid+1,r,rs[now]);
}
void update(int l,int r,int &now,int last,int pos){
    now=++cnt;
    ls[now]=ls[last];
    rs[now]=rs[last];
    sum[now]=sum[last]+1;
    if(l==r) return ;
    int mid=(l+r)>>1;
    if(pos<=mid) update(l,mid,ls[now],ls[last],pos);
    else update(mid+1,r,rs[now],rs[last],pos);
}
LL query(int L,int R,int l,int r,int t1,int t2){
    if(l>=L&&r<=R)
        return 1LL*sum[t2]-sum[t1];
    int mid=(l+r)>>1;
    LL re=0;
    if(mid>=L) re+=query(L,R,l,mid,ls[t1],ls[t2]);
    if(mid<R) re+=query(L,R,mid+1,r,rs[t1],rs[t2]);
    return re;
}
void initdfs(int u){ //打标记,用于后续主席树query
    book[u]=++cnt; //dfs先序遍历
    p[cnt]=u;  //对应下标
    cson[u]=1;//计算子树数量,用于后续query
    for(int i=0;i<(int)g[u].size();i++){
        int to=g[u][i];
        initdfs(to);
        cson[u]+=cson[to];
    }
}
 
void ansdfs(int u){ //计算最后答案
    ans[u]+=sumabs[u];
    for(int i=0;i<(int)g[u].size();i++){
        int to=g[u][i];
        ansdfs(to);
        ans[u]+=ans[to];
    }
}
int main(){
    int n,k;scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]),v.push_back(a[i]);
    int x;for(int i=2;i<=n;i++) scanf("%d",&x),g[x].push_back(i);
    sort(v.begin(),v.end()); 
    v.erase(unique(v.begin(),v.end()),v.end());
    initdfs(1);
    cnt=0;int sz=v.size();//离散化大小
    build(1,sz,root[0]);//建立树形,可有可无
    for(int i=1;i<=n;i++){
        update(1,sz,root[i],root[i-1],getindex(a[p[i]]));
    }
    for(int i=1;i<=n;i++){
        int ll=getindex(a[i]-k),rr; //lowerbound
        rr=upper_bound(v.begin(),v.end(),a[i]+k)-v.begin();//注意这里+1-1
        sumabs[i]=query(ll,rr,1,sz,root[book[i]-1],root[book[i]+cson[i]-1]);//注意下标对应,树对应,区间对应,可有画一个特别的树看一看
    }
    ansdfs(1);
    for(int i=1;i<=n;i++) printf("%lld\n",ans[i]);//再cout<<endl;是狗
    return 0;
}
 

 不离散直接搞也行,两个复杂度差不多

这里换了一种求当前询问树编号的方法

发现如图的规律,直接dfs过程中记录一下需要查询的树的编号即可,为红字和绿字。

也就是当前编号,和当前编号-当前编号节点的子孙结点数量

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int max_n =1e5+5,logn=60;
vector<int>g[max_n];
 
int a[max_n];int p[max_n];
int root[max_n*logn],ls[max_n*logn],rs[max_n*logn];
int sum[max_n*logn];
int cnt;
int returnV; //当前return最大的编号
int book[max_n];
int sumabs[max_n];
LL ans[max_n];
 
void build(int l,int r,int &now){
    now=++cnt;
    sum[now]=0;
    if(l==r) return;
    int mid=(l+r)>>1;
    build(l,mid,ls[now]);
    build(mid+1,r,rs[now]);
}
void update(int l,int r,int &now,int last,int pos){
    now=++cnt;
    ls[now]=ls[last];
    rs[now]=rs[last];
    sum[now]=sum[last]+1;
    if(l==r) return ;
    int mid=(l+r)>>1;
    if(pos<=mid) update(l,mid,ls[now],ls[last],pos);
    else update(mid+1,r,rs[now],rs[last],pos);
}
int query(int L,int R,int l,int r,int t1,int t2){
    if(l>=L&&r<=R)
        return 1LL*sum[t2]-sum[t1];
    int mid=(l+r)>>1;
    LL re=0;
    if(mid>=L) re+=query(L,R,l,mid,ls[t1],ls[t2]);
    if(mid<R) re+=query(L,R,mid+1,r,rs[t1],rs[t2]);
    return re;
}
void initdfs(int u){ //打标记,用于后续主席树query
    book[u]=returnV;
    for(int i=0;i<(int)g[u].size();i++){
        int to=g[u][i];
        initdfs(to);
    }
    p[++cnt]=u; //映射进去
    returnV=cnt;
}
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
 
void ansdfs(int u){
    ans[u]+= 1LL*sumabs[u];
    for(int i=0;i<(int)g[u].size();i++){
        int to=g[u][i];
        ansdfs(to);
        ans[u]+=ans[to];
    }
}
int main(){
    int n,k;n=read();k=read();
    for(int i=1;i<=n;i++) {
        a[i]=read();
    }
    for(int i=2;i<=n;i++){
        int x;x=read();g[x].push_back(i);
    }
    initdfs(1);
    cnt=0;int sz=1e9;
    for(int i=1;i<=n;i++){
        int now=p[i];
        update(1,sz,root[i],root[i-1],a[now]);
        sumabs[now]=query(a[now]-k,a[now]+k,1,sz,root[book[now]],root[i]);
    }
    ansdfs(1);
    for(int i=1;i<=n;i++) cout<<ans[i]<<'\n';
    return 0;
}

4.23更:树状数组更简单!!

离散化后用树状数组维护每个数出现的次数,树状数组按照遍历到的依次更新到树状数组,每次dfs到这个点和回溯的时候分别更新,

比如遍历到u的时候,ans[u]需要先减去(1,2)对他的影响,回溯到u时候,ans[u]再加上(1,2,3,4,5)对他的影响,这样两部分就是(3,4,5)对u 的影响,即u的子树对u的影响。

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int max_n = 1e5+5;
int n,k,a[max_n];
vector<int>g[max_n];
vector<int>v;
int bit[max_n];
LL ans[max_n];
void update(int x,int i,int nn){
    while(i<=nn){
        bit[i]+=x;i=i+(i&-i);
    }
}
int query(int i){
    int re=0;while(i>0){
        re=re+bit[i];i=i-(i&-i);
    }
    return re;
}
void dfs(int u){
    //访问到之前
    int l=lower_bound(v.begin(),v.end(),a[u]-k)-v.begin()+1;//1-n
    int r=upper_bound(v.begin(),v.end(),a[u]+k)-v.begin()+1-1;//1-n
    ans[u]=ans[u]-1LL*(query(r)-query(l-1));
    for(int i=0;i<(int)g[u].size();i++){
        int to=g[u][i];
        dfs(to);
        ans[u]=ans[u]+1LL*ans[to];
    }
    //访问到之后
    int idx=lower_bound(v.begin(),v.end(),a[u])-v.begin()+1;
    update(1,idx,n);
    l=lower_bound(v.begin(),v.end(),a[u]-k)-v.begin()+1;//1-n
    r=upper_bound(v.begin(),v.end(),a[u]+k)-v.begin()+1-1;//1-n
    ans[u]=ans[u]+1LL*(query(r)-query(l-1));
}
int main(){
    scanf("%d%d",&n,&k);for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        v.push_back(a[i]);
    }
    sort(v.begin(),v.end());v.erase(unique(v.begin(),v.end()),v.end());
    for(int i=2;i<=n;i++){
        int sc;scanf("%d",&sc);g[sc].push_back(i);
    }
    dfs(1);
    for(int i=1;i<=n;i++)printf("%lld\n",ans[i]);
    return 0;
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值