zoukankan      html  css  js  c++  java
  • [九省联考2018] IIIDX 线段树+贪心

    题目:

      给出 k 和 n 个数,构造一个序列使得 d[i]>=d[i/k] ,并且字典序最大。

    分析:

      听说,当年省选的时候,这道题挡住了大批的高手,看上去十分简单,实际上那道弯段时间内是转不过来的。

      首先,一个套路是,将这个序列的关系抽象成一棵树,i的父亲是floor(i/k),我们要要求子树内部的点的权值都比父亲大。

      我们观察子任务的特殊限制,di不一样?

      我们想,把原序列从大到小排序,在树上dfs给点赋值,在给一个点赋值时,要在序列上预留出siz[这棵子树]的位置,用来给子树内部的点赋值(排序就是为了方便在序列上预留位置)。

      但是,如果有重复的元素就办不了了,比如n=4, k=2 序列是1,1,1,2.

      排好应该是1 1 2 1,但是我们上面的方法会排成1 1 1 2

      为什么呢?

      我们从大到小排序,应该得到2 1 1 1.我们递归,首先是树根节点,需要预留4个位置,所以肯定要选最后一个(1),然后递归到第一个子树(序号为2),size为2,需要在这个子树留两个位置,所以我们把第二个1给了序号2这个位置,然后那个2就给了它的儿子(序号4)但是实际上,我们可以给它留一个1,把这个4给位置3.

      我这么分析会很乱,但是我们于情于理考虑一下,我们解决完了位置2,若想字典序最大,我们应该先最大化3这个位置。

      所以我们应该在给序号2找到最大值之后,应该为他的子树预留一些值,使这些值在合法的基础上尽可能小。

      所以我们应该排序后,建立一个序列c,c[i]的值代表排好序的序列上,i及i左边的所有值还剩下多少个可以选的。

      (序列c一开始肯定是1,2,3,4,5……)

      然后我们每次为一个点赋值,应该找到最靠左的一个位置i,使所有j>=i满足c[j]>=siz[这个点的子树]。

      并且选完之后,要找到这个位置往右最右边和他值相等的那个位置(前提是没有被用过的)。

      然后,用区间减法计算贡献,之后接着处理这个点的兄弟节点,没有了兄弟节点之后在进入某个点的子节点。

      进入一个子节点时,要将之前预留的位置(区间减去的数值)加回来,但是已经分配出去的(父亲的)权值就不要加回来了。

      所以是siz-1

    代码:

     1 #include<bits/stdc++.h>
     2 #define db double
     3 using namespace std;
     4 const int N=500005,inf=1e9;
     5 struct segtree{
     6     int l,r,ls,rs,mn,lz;
     7 }t[N*4];int a[N],b[N],ans[N],m,rt;
     8 db k;int n,siz[N],fa[N],cnt[N],o=0;
     9 bool cmp(int u,int v){return u>v;}
    10 void pushup(int cur){
    11     int ls=t[cur].ls,rs=t[cur].rs;
    12     t[cur].l=t[ls].l;t[cur].r=t[rs].r;
    13     t[cur].mn=min(t[ls].mn,t[rs].mn);
    14 } void pushdown(int cur){
    15     int ls=t[cur].ls,rs=t[cur].rs,d=t[cur].lz;
    16     t[ls].mn+=d;t[ls].lz+=d;
    17     t[rs].mn+=d;t[rs].lz+=d;
    18     t[cur].lz=0;return ;
    19 } void build(int x,int l,int r){
    20     if(l==r){
    21         t[x].l=t[x].r=l;t[x].ls=t[x].rs=-1;
    22         t[x].mn=l;return ;
    23     } int mid=l+r>>1;
    24     t[x].ls=o++;t[x].rs=o++;
    25     build(t[x].ls,l,mid);build(t[x].rs,mid+1,r);
    26     pushup(x);return ;
    27 } void update(int x,int l,int r,int c){
    28     if(l<=t[x].l&&t[x].r<=r) 
    29     {t[x].mn+=c;t[x].lz+=c;return ;}
    30     pushdown(x);int mid=t[x].l+t[x].r>>1;
    31     if(l<=mid) update(t[x].ls,l,r,c);
    32     if(mid<r) update(t[x].rs,l,r,c);
    33     pushup(x);return ;
    34 } int query(int x,int p){
    35     if(t[x].l==t[x].r) 
    36     return t[x].mn>=p?t[x].l:t[x].l+1;
    37     pushdown(x);int mid=t[x].l+t[x].r>>1;
    38     if(p<=t[t[x].rs].mn) return query(t[x].ls,p);
    39     else return query(t[x].rs,p);return 0; 
    40 } int main(){
    41     scanf("%d%lf",&n,&k);rt=o++;
    42     for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    43     sort(a+1,a+1+n,cmp);build(rt,1,n);
    44     for(int i=n-1;i;i--)
    45     if(a[i]==a[i+1]) cnt[i]=cnt[i+1]+1;
    46     else cnt[i]=0;
    47     for(int i=1;i<=n;i++)
    48     fa[i]=(int)floor(i/k),siz[i]=1;
    49     for(int i=n;i;i--) siz[fa[i]]+=siz[i];
    50     for(int i=1;i<=n;i++){
    51         if(fa[i]&&fa[i]!=fa[i-1])
    52         update(rt,ans[fa[i]],n,siz[fa[i]]-1);
    53         int x=query(rt,siz[i]);ans[i]=x;
    54         x+=cnt[x];cnt[x]++;x-=(cnt[x]-1);
    55         update(rt,x,n,-siz[i]);
    56     } for(int i=1;i<=n;i++)
    57     printf("%d ",a[ans[i]]);
    58     return 0;
    59 }
    贪心+线段树
  • 相关阅读:
    Scikit-learn学习记录【持续更新】——Scikit-learn框架的入门介绍
    【步骤超详细】mac系统 通过anaconda配置Pycharm的scikit-learn环境
    与高哥聊天有感
    蓝杰学长学姐交流记录
    Robcup2D足球学习记录【2020.01.06】
    分治算法
    多所学校就业报告分析总结
    想要半年之内拿到30万大厂的offer?看这里!!!
    C语言变量的存储类别
    C语言函数入门
  • 原文地址:https://www.cnblogs.com/Alan-Luo/p/10459252.html
Copyright © 2011-2022 走看看