[bzoj5249][2018多省省队联测]IIIDX

给我的感觉是需要先言传才能意会。。。真的可以在考场上想到这么神的算法吗??

#include<bits/stdc++.h>
using namespace std;
const int N=500010;
const int inf=1e9;
inline int read(){
    int r=0,c=getchar();
    while(!isdigit(c))c=getchar();
    while(isdigit(c))
    r=r*10+c-'0',c=getchar();
    return r;
}
struct Edge{
    int to,nxt;
}e[N<<1];
int head[N],cnt=1;
void add(int u,int v){
    e[cnt]=(Edge){v,head[u]};
    head[u]=cnt++;
}
int n;double k;
int a[N],b[N],las[N],fa[N],siz[N];
void dfs(int u){
    siz[u]=1;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        dfs(v);siz[u]+=siz[v];
    }
}
struct Node{
    int l,r,add,mn;
}T[N<<2];
#define ls o<<1
#define rs o<<1|1
#define L T[o].l
#define R T[o].r
#define M (L+R>>1)
void pp(int o){
    T[o].mn=min(T[ls].mn,T[rs].mn);
}
void pd(int o){
    if(T[o].add){
        T[ls].add+=T[o].add;
        T[rs].add+=T[o].add;
        T[ls].mn+=T[o].add;
        T[rs].mn+=T[o].add;
        T[o].add=0;
    }
}
void build(int o,int l,int r){
    if(l==r){
        T[o]=(Node){l,r,0,l};
        return;
    }
    T[o]=(Node){l,r,0,inf};
    build(ls,L,M);
    build(rs,M+1,R);
    pp(o);
}
int ql,qr,v;
void upd(int o){
    if(ql<=L&&R<=qr){
        T[o].add+=v;
        T[o].mn+=v;
        return;
    }
    pd(o);
    if(ql<=M)upd(ls);
    if(qr>M) upd(rs);
    pp(o);
}
int find(int o){
    if(L==R)return T[o].mn>=v?L:L+1;
    pd(o);
    if(T[rs].mn>=v)return find(ls);
    else return find(rs);
}
bool comp(int p,int q){
    return p>q;
}
void init(){
    n=read();cin>>k;
    for(int i=1;i<=n;i++){
        fa[i]=(double)i/k+1e-10;//浮点数有坑 
        add(fa[i],i);b[i]=read();
    }
    sort(b+1,b+n+1,comp);
    for(int i=1;i<=n;i++)
    if(b[i]==b[i-1])
    a[i]=a[i-1],las[a[i]]=i;
    else a[i]=i,las[a[i]]=i;
    dfs(0);
}
int ans[N],vis[N];
void solve(){
    build(1,1,n);//建线段树,i的初值为i 
    for(int i=1;i<=n;i++){
        if(fa[i]&&!vis[fa[i]]){//如果父亲的预定没被撤销 
            vis[fa[i]]=1;//只撤销一次所以要标记一下 
            ql=ans[fa[i]],qr=n;
            v=siz[fa[i]]-1;upd(1);//父亲修改的区间加上siz[fa]-1 
        }
        v=siz[i];int x=a[find(1)];//在线段树上二分查找第一个右侧值及本身全部大于等于siz[i]的结点
        v=-v;ans[i]=ql=las[x]--,qr=n;upd(1);//值已经确定,选择最靠右的那个,并把它及它右边的值减去siz[i] 
    }
    for(int i=1;i<=n;i++)
    printf("%d ",b[ans[i]]);
}
int main(){
    init();
    solve();
}

 

posted @ 2018-04-11 11:14  orzzz  阅读(299)  评论(0编辑  收藏  举报