CSUST 2033 大富翁 (离线树状数组 + 线段树求动态第 k 小)

链接: 大富翁

在这里插入图片描述
题意:
给一颗树 ,连接 n 个房间 ,n 个大富翁分别住在这 n 个房间,n 人 依次那 走第 k小的砝码 , k 的定义是 每个节点 的每个儿子节点及其对应子树中比当前节点权值大的节点数的最小值( 有点绕)。问每个人取走砝码的重量。
思路:

  1. 首先要解决的肯定是每个节点的子树内比父节点权值大的点的个数,也就是求给定区间内比 k 大的数的个数,这里不过是加了个 dfs 序,有很多写法 ,分块 ,离线树状数组,主席树?(这个貌似还不会),这里用了离线树状数组,离线树状数组,大概就是按权值顺序一边插入一边查询。
  2. 解决了操作序列的问题,然后就是那砝码了,问题大概变成了给 1-n 重量的砝码,给一个序列 a ,每次拿走第 a[i] 小的砝码,求拿走砝码的顺序。
  3. 这里可以用线段树实现类似二分的操作 ,例如当前要那 第 k 大,如果线段树左区间的 存在的数的个数(没有被取走的数) 大于 k,我就继续找左区间第 k小,否则我就找有区间的第 k-lsum小(lsum指左区间存在的数的个数),知道区间长为 1 就找到了第 k小。

代码:

#include<iostream>
#include<cstdio>
#include<map>
#include<math.h>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
typedef unsigned long long ll;
const int maxn=1e6+7;
int c[maxn],n,m,T,ans[maxn],ca=1,num[maxn],root,tot,head[maxn];
int le[maxn],ri[maxn],id=1,fa[maxn],op[maxn],anss[maxn],sz[maxn];
int sum[maxn<<2];
struct node{
      int l,r,val,id;
}b[maxn];
struct stu{
       int id,val;
}a[maxn];
struct ex{
      int v,next;
}e[maxn];
bool cmp1(node a,node b){
    return a.val>b.val;
}
bool cmp2(stu a,stu b){
    return a.val>b.val;
}
void pushup(int rt){
     sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void build(int l,int r,int rt){
     if(l==r){
        sum[rt]=1;
        return ;
     }
     ll m=(l+r)>>1;
     build(l,m,rt<<1);
     build(m+1,r,rt<<1|1);
     pushup(rt);
}
void update1(int i,int c,int l,int r,int rt){     
     if(l==r) {
        sum[rt]=c;
        return;
     }
     ll m=(l+r)>>1;
     if(i<=m) update1(i,c,l,m,rt<<1);
     else update1(i,c,m+1,r,rt<<1|1);
     pushup(rt);
}
int query(int k,int l,int r,int rt){              // 求 第 k 大
    if(l==r) return l;
    int m=(l+r)>>1;
    if(sum[rt<<1]>=k) return query(k,l,m,rt<<1);
    else return query(k-sum[rt<<1],m+1,r,rt<<1|1);
}
void add(int u, int v){
     e[tot].next=head[u];
     e[tot].v=v;
     head[u]=tot++;
}
void dfs(int u,int pre){
     le[u]=id++;sz[u]=1;
     for(int i=head[u];i!=-1;i=e[i].next){
         int v=e[i].v;
         if(v==pre) continue;
         fa[v]=u;
         dfs(v,u);
         sz[u]+=sz[v];
     }
     ri[u]=id-1;
}
void dfs1(int u,int pre){
    if(sz[u]==1) op[u]=0;
    for(int i = head[u]; i != -1; i = e[i].next){
        int v=e[i].v;
        if(v==pre) continue;
        dfs1(v,u);
        op[u]=min(op[u],ans[v]);
    }
}
int  lowbit(int x){
     return x&(-x);
}
void update(int x){
     while(x<=n){
          c[x]+=1;
          x+=lowbit(x);
     }
}
int getsum(int x){
    int sum=0;
    while(x>0){
        sum+=c[x];
        x-=lowbit(x);
    }
    return  sum;
}
int main (){
         memset(head,-1,sizeof(head));
         memset(op,0x3f3f3f3f,sizeof(op));
         scanf("%d",&n);
         for(int i = 1,u; i <= n; i ++ ){
            scanf("%d",&u);
            num[u] = i;
            if(u == 1) root = i; 
         }
         for(int i = 0,u,v; i< n-1; i++){
             scanf("%d%d",&u,&v);
             add(num[u],num[v]);
             add(num[v],num[u]);
         }
         dfs(root,-1);                  //求dfs序
         m=1;
         for(int i = 1; i <= n ;i ++){  // 处理需要查询的值,离线操作
             if(i==root) continue;      
             b[m].id=i,
             b[m].l=le[i];
             b[m].r=ri[i];
             b[m++].val=fa[i];
         }
         m=1;
         for(int i = 1; i <= n; i ++){  //存入每个点的权值
             if(i==root) continue;
             a[m].id=le[i];
             a[m++].val=i;
         }
         sort(a+1,a+m+1,cmp2);         //从大到小 插入  查询
         sort(b+1,b+m+1,cmp1);
         int k=1;
         for(int i = 1; i < m; i ++){
            while(k < m && a[k].val > b[i].val){
                 update(a[k].id);
                 k++;
            }
            ans[b[i].id]=getsum(b[i].r)-getsum(b[i].l-1);
         }
         dfs1(root,-1);                 //得到操作序列
         build(1,n,1);
         for(int i = 1; i <= n; i ++){ //线段树求 连续区间 第 k 大 相当于二分的思路
              anss[i]= query (op[i]+1,1,n,1);
              update1(anss[i],0,1,n,1);
         }
         for(int i=1;i<=n;i++) printf ("%d\n",anss[i]);  
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值