「九省联考 2018」IIIDX 贪心 线段树

【题目背景】

Osu 听过没?那是 Konano 最喜欢的一款音乐游戏,而他的梦想就是有一天自己也能做个独特酷炫的音乐游戏。现在,他在世界知名游戏公司 KONMAI 内工作,离他的梦想也越来越近了。

这款音乐游戏内一般都包含了许多歌曲,歌曲越多,玩家越不易玩腻。同时,为了使玩家在游戏上氪更多的金钱花更多的时间,游戏一开始一般都不会将所有曲目公开,有些曲目你需要通关某首特定歌曲才会解锁,而且越晚解锁的曲目难度越高。

【题目描述】

这一天,Konano 接到了一个任务,他需要给正在制作中的游戏《IIIDX》安排曲目的解锁顺序。游戏内共有 n n 首曲目,每首曲目都会有一个难度 d d ,游戏内第 i i 首曲目会在玩家 Pass 第 ik ⌊ i k ⌋ 首曲目后解锁( x ⌊ x ⌋ 为下取整符号)若 ik=0 ⌊ i k ⌋ = 0 ,则说明这首曲目无需解锁

举个例子:当 k=2 k = 2 时,第 1 1 首曲目是无需解锁的( 12=0 ⌊ 1 2 ⌋ = 0 ),第 7 7 首曲目需要玩家 Pass 第 72=3 ⌊ 7 2 ⌋ = 3 首曲目才会被解锁。

Konano 的工作,便是安排这些曲目的顺序,使得每次解锁出的曲子的难度不低于作为条件需要玩家通关的曲子的难度,即使得确定顺序后的曲目的难度对于每个 i i 满足 didik d i ≥ d ⌊ i k ⌋

当然这难不倒曾经在信息学竞赛摸鱼许久的 Konano。那假如是你,你会怎么解决这份任务呢?

【数据范围】

测试点编号 n n k k d d 特殊限制
1 1 1n10 1 ≤ n ≤ 10 k=2 k = 2 1d100 1 ≤ d ≤ 100 保证 di d i 互不相同
2 2 k=3 k = 3
3 3 k=1.1 k = 1.1
4 4 k=n k = n
5 5 1<k100 1 < k ≤ 100
6 6
7 7 1n2000 1 ≤ n ≤ 2000 k=2 k = 2 1d109 1 ≤ d ≤ 10 9 保证 di d i 互不相同
8 8
9 9 k=3 k = 3 保证 di d i 互不相同
10 10
11 11 1<k109 1 < k ≤ 10 9 保证 di d i 互不相同
12 12
13 13 1n500000 1 ≤ n ≤ 500000 k=2 k = 2
14 14 k=3 k = 3
15 15 1<k109 1 < k ≤ 10 9 保证 di d i 互不相同
16 16
17 17
18 18
19 19
20 20

题解

首先考虑部分分的做法。
很显然可以发现,本题对于歌曲解锁的顺序限制是一个树形的结构,这样一来,题目的要求就可以变成把一堆数放到一颗树上,使得儿子均比父亲来得大,并且要求解最大。
贪心地来想,好像按树的后序遍历从大到小放数字就是最优解了。
然而   ⋯   ,如果数有相同,那么就会出现一些不对的情况。
假设我们的序列是这样

k=2 , d={6,6,6,9} k = 2   ,   d = { 6 , 6 , 6 , 9 }

那么贪心: {6,6,6,9} { 6 , 6 , 6 , 9 }
然而正解: {6,6,9,6} { 6 , 6 , 9 , 6 }
于是我们的贪心就不对了,这个时候该怎么办呢?
我们仍然是贪心,只不过这一次,我们保证每一个节点都确定了尽量大的数,再依次往后确定。
举个例子,假如 d={9,9,8,2,4,4,3,5,3} d = { 9 , 9 , 8 , 2 , 4 , 4 , 3 , 5 , 3 } ,排序后 {2,3,3,4,4,5,8,9,9} { 2 , 3 , 3 , 4 , 4 , 5 , 8 , 9 , 9 }
若第一个点的子树大小为 5 5 那么这个点的值就是第 5 5 大的数字, 4 4
不过 4 4 有很多个啊,我们就钦定取最左的 4 4 ,也就是第 6 6 个数。那么这样一来,我们就要在区间 [6,9] [ 6 , 9 ] 里面预留 4 4 个节点放在这个点的子树中。
怎么解决这样的预留的问题呢?
我们记 fi f i 表示第 i i 个点右边已经被预留了 fi f i 个数了,这样我们就能知道 i i 右边可用的数有 ifi i − f i 个。
对于上面这种情况,我们就将 f1 f 1 f3 f 3 都加上 4 4
一般地,当我们处理到第 i i 个节点时,设其子树大小为 sizei s i z e i
那么找一个最小的位置 p p ,使得 jfj (jp)sizei j − f j   ( j ≥ p ) ≤ s i z e i ,并将 p p 上的数给 i i 这个点就行了。若是 i i 有父亲,那么就要把 i i 父亲节点的预留给去掉。

也可以换一种理解方式,就是我们每次贪心地选一个数以后,都要保证预留一些树来放在这个点的子树中,于是我们就一步步做然后保证不矛盾就行了。接着在这个基础上使得解最大。

于是我们开一颗线段树,就能维护上面那些区间修改和查询了。
其实线段树只需要直接维护 ifi i − f i 就行了。
(我才不会说我从小到大排序是因为不想用 greater greater 的)

代码

60分贪心:
#include
using namespace std;
#define R register
const int maxn = 5e5+10;
struct Edge{
    int To;
    Edge *Next;
}*Head[maxn];
inline void Add(R int u,R int v){
    static Edge E[maxn],*e=E;
    *e=(Edge){v,Head[u]};Head[u]=e++;
}
int n,d[maxn],*c=d;
double k;
int fa[maxn];
int Ans[maxn];
void dfs(R int u){
    for(R Edge *i=Head[u];i;i=i->Next){
        dfs(i->To);
    }
    if(u)Ans[u]=*++c;
}
int main(){
    scanf("%d %lf",&n,&k);
    for(R int i=1;i<=n;++i)scanf("%d",d+i);
    sort(d+1,d+1+n,greater<int>());
    for(R int i=n;i;i--){
        fa[i]=(int)(1.0*i/k);
        Add(fa[i],i);
    }
    dfs(0);
    for(R int i=1;i<=n;++i)printf("%d ",Ans[i]);
    return 0;
}
Accept
#include 
#define R register
#define LL long long
template<class TT>inline TT Max(R TT a,R TT b){return atemplate<class TT>inline TT Min(R TT a,R TT b){return ausing namespace std;
template<class TT>inline void read(R TT &x){
    x=0;R bool f=false;R char c=getchar();
    for(;c<48||c>57;c=getchar())f|=(c=='-');
    for(;c>47&&c<58;c=getchar())x=(x<<1)+(x<<3)+(c^48);
    (f)&&(x=-x);
}
int happy;
const int maxn = 1000010;

int n,d[maxn];
double k;

//Graph
int tp;
struct Edge{
    int to;
    Edge *next;
}*head[maxn];
inline void Add(R int u,R int v){
    static Edge E[maxn],*e=E;
    *e=(Edge){v,head[u]};head[u]=e++;
}
//end

//segment_tree
#define Ls (i<<1)
#define Rs (i<<1|1)
int s[1<<20],t[1<<20];
inline void push_down(R int i){
    if(t[i]){
        R int x=t[i];
        t[Ls]+=x;
        t[Rs]+=x;
        s[Ls]+=x;
        s[Rs]+=x;
        t[i]=0;
    }
}
#define defvar R int l,R int r,R int i
void build(defvar){
    if(l==r){s[i]=n-l+1;return;}
    R int mid=l+r>>1;
    build(l,mid,Ls);
    build(mid+1,r,Rs);
    s[i]=Min(s[Ls],s[Rs]);
}
void add(defvar,R int ql,R int qr,R int qx){
    if(ql<=l&&r<=qr){
        s[i]+=qx;
        t[i]+=qx;
        return;
    }
    push_down(i);
    R int mid=l+r>>1;
    if(ql<=mid)add(l,mid,Ls,ql,qr,qx);
    if(qr>mid)add(mid+1,r,Rs,ql,qr,qx);
    s[i]=Min(s[Ls],s[Rs]);
}
int query(defvar,R int qp,R int qx){
    R int mid=l+r>>1,x=0;
    if(qp<=l){
        if(s[i]>=qx)return 0;
        if(l==r)return l-1;
        push_down(i);
        if(s[Ls]>=qx){
            x=query(mid+1,r,Rs,qp,qx);
            s[i]=Min(s[Ls],s[Rs]);
            return x;
        }
        if(s[Ls]if(x)return x;
        }
        return l-1;
    }
    push_down(i);
    if(qp<=mid)x=query(l,mid,Ls,qp,qx);
    if(!x)x=query(mid+1,r,Rs,qp,qx);
    s[i]=Min(s[Ls],s[Rs]);
    return x;
}
//end

//get size
int siz[maxn];
void dfs(R int u){
    siz[u]=1;
    for(R Edge *i=head[u];i;i=i->next){
        dfs(i->to);
        siz[u]+=siz[i->to];
    }
}
//end
int fa[maxn];
int st[maxn],pos[maxn],lth[maxn];
int main(){
    read(n);
    happy=scanf("%lf",&k);
    for(R int i=1;i<=n;++i)read(d[i]);
    for(R int i=1;i<=n;++i){
        fa[i]=1.0*i/k;
        Add(fa[i],i);
    }
    dfs(0);
    sort(d+1,d+1+n);
    build(1,n,1);
    for(R int i=1;i<=n;++i){
        if(d[i]!=d[i-1])st[i]=i;
        else st[i]=st[i-1];
    }
    for(R int i=1,p;i<=n;++i){
        p=query(1,n,1,pos[fa[i]]+1,siz[i]);
        if(!p)p=n-siz[i]+1;
        p=st[p]+lth[st[p]];
        printf("%d ",d[p]);
        lth[st[p]]++;
        pos[i]=p;
        add(1,n,1,pos[fa[i]]+1,p,-siz[i]);
    }
    return 0;
}

你可能感兴趣的:(题解们)