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

链接: 大富翁

CSUST 2033 大富翁 (离线树状数组 + 线段树求动态第 k 小)_第1张图片
题意:
给一颗树 ,连接 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
#include
#include
#include
#include
#include
#include
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]);  
}

你可能感兴趣的:(数据结构)