省选九省联考T2 IIIDX(线段树)

Posted david--lj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了省选九省联考T2 IIIDX(线段树)相关的知识,希望对你有一定的参考价值。

题目传送门:https://www.luogu.org/problemnew/show/P4364

期中考后记:期中考刚考完,感觉不咋滴,年排第3。我抗压力太差了。。期末得把rank1抢回来。

本来感觉是个瞎贪心的(对每个歌曲的先决的歌曲为父子关系建树,然后每次找尽量大的孩子)。。其实不是。

贪心仅能骗60分。(省选题不会水的)

前面那个贪心只是瞎YY的。YY的贪心有一定几率出错。

为啥会出错,我一会儿讲。

我们正经得想一个贪心。其实问题变为:

for(i=1;i<=n;++i){

找一个难度为p的歌曲并删去,使在i后面的节点能依照没有p歌曲的歌单下不会没有难度足够大的歌曲的情况下,p尽量大,第i个歌曲难度就是p

}

其实这个贪心的思路非常好想(我最开始想的就是这个,后来去打暴力了,就用了第一个)

我们把设选了歌曲p后需要Ap个大于等于p的歌曲(包括p)。那么,我们只要计算出所有Ap,不就爽歪歪了吗?

最开始,我们将歌曲难度降序排序记为A,Ap=p。

写个伪代码

for(i=1;i<=n;++i){

  找到最靠右的节点k使Ak-1<siz[i](i的子树大小)

  ans[i]=k;

  将ak+1.ak+2....an都减去siz[i];

}

for(i=1;i<=n;++i)输出d[ans[i]]

好了还记得最开始讲的贪心为啥会错吗?

因为,我刚才写的伪代码是错的(错的地方和第一个贪心一毛一样)

正确的在此

for(i=1;i<=n;++i){

  找到最靠右的节点k使Ak-1<siz[i](i的子树大小)

  如果相同的几个歌曲和k的难度一样,那么,将k移到在序列A中最靠左的那个;

  ans[i]=k;

  将ak+1.ak+2....an都减去siz[i];

}

为啥要那样干?反正没有后效性,而且贪心吗,就要贪得彻底,预留给自己的子树尽量多的节点。

代码:

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+10;
int n,d[N],ans[N],siz[N],fa[N],wxy[N<<2],dr[N<<2],hd[N],pos,lovewxy[N];
double k;
bool cmp(int x,int y){return  x>y;}
bool vis[N];
void pushup(int rt){wxy[rt]=min(wxy[rt<<1],wxy[rt<<1|1]);}
void pushdown(int rt){
    if(dr[rt]){
        dr[rt<<1]+=dr[rt];
        dr[rt<<1|1]+=dr[rt];
        wxy[rt<<1]+=dr[rt];
        wxy[rt<<1|1]+=dr[rt];dr[rt]=0;
    }
}
void build(int rt,int l,int r){
    if(l==r){
        wxy[rt]=l;return;
    }
    int mid=l+r>>1;
    build(rt<<1,l,mid);build(rt<<1|1,mid+1,r);pushup(rt);
}
void motify(int rt,int l,int r,int ql,int qr,int d){
    if(ql<=l && r<=qr){
        wxy[rt]+=d;dr[rt]+=d;return;
    }pushdown(rt);
    int mid=l+r>>1;
    if(mid>=ql)motify(rt<<1,l,mid,ql,qr,d);
    if(mid+1<=qr)motify(rt<<1|1,mid+1,r,ql,qr,d);
    pushup(rt);
}
int query(int rt,int l,int r,int lst){
    if(l==r)return wxy[rt]>=lst ? l:l+1;
    pushdown(rt);int mid=l+r>>1;
    if(wxy[rt<<1|1]<lst)return query(rt<<1|1,mid+1,r,lst);
    return query(rt<<1,l,mid,lst);
}
void dfs(int u){
    if(u>n)return;
    int l,r;double tmp=(double)u*k;
    l=tmp-1;
    while(l<tmp)++l;
    tmp=(double)(u+1)*k;
    r=tmp-1;while(r<tmp)++r;
    --r;siz[u]=1;
    for(int i=r;i>=l;--i)
    if(i<=n)
    dfs(i),siz[u]+=siz[i],fa[i]=u;
}
int main(){
    scanf("%d%lf",&n,&k);
    int i;for(i=1;i<=n;++i)scanf("%d",&d[i]);sort(d+1,d+1+n,cmp);d[n+1]=-1;
    for(i=n;i>0;--i)hd[i]= d[i]==d[i+1] ? pos:++pos;
    for(i=1;i<=n;++i)lovewxy[hd[i]]=max(lovewxy[hd[i]],i);
    int p=k+1;
    while(p>=k)
    --p;
    for(i=p;i>0;--i){dfs(i);}build(1,1,n);
    for(i=1;i<=n;++i){
        if(fa[i]&&!vis[fa[i]]){
            motify(1,1,n,ans[fa[i]],n,siz[fa[i]]-1);vis[fa[i]]=1;
        }
        int tp=query(1,1,n,siz[i]);
        tp=lovewxy[hd[tp]];
        --lovewxy[hd[tp]];
        ans[i]=tp;motify(1,1,n,ans[i],n,-siz[i]);
    }
    printf("%d",d[ans[1]]);
    for(i=2;i<=n;++i)printf(" %d",d[ans[i]]);
}

 

以上是关于省选九省联考T2 IIIDX(线段树)的主要内容,如果未能解决你的问题,请参考以下文章

loj2472 「九省联考 2018」IIIDX

[luogu] P4364 [九省联考2018]IIIDX(贪心)

[九省联考2018]IIIDX

解题:九省联考2018 IIIDX

noi省选 [九省联考2018]一双木棋题解(状压dp)

[九省联考2018]秘密袭击coat 伪·题解