zoukankan      html  css  js  c++  java
  • 题解 LOJ2472 「九省联考 2018」IIIDX

    如果(lfloorfrac{i}{k} floor eq 0),就从(i)(lfloorfrac{i}{k} floor)连边,则可以得到一个森林。问题转化为:给森林里每个节点安排一个点权,在保证后代的点权(geq)祖先点权的前提下,使按编号排列时的字典序最大。

    把所有权值按从小到大排序。

    自然想到的一种贪心是,把编号最大的一段,留给以(1)为根的树。例如,以(1)为根的树大小为( ext{sz}_1),则我们把(val_{n- ext{sz}_1+1}dots val_n)留给以(1)为根的树,并且显然,(1)号节点的权值此时就是(val_{n- ext{sz}_1+1})。以此类推,按编号从小到大,每个节点(的子树)都得到连续的一段长度等于其子树大小的权值。容易发现,由于这棵树的性质,深度小的节点的编号,一定小于深度大的节点的编号,所以直接按编号顺序从小到大确定即可。

    这种贪心在权值无重复时显然是正确的。但如果有重复的权值,就会出问题。例如:(n=4;k=2;a=(1,1,1,2))。正确的答案应该是:((1,1,2,1))。而我们贪心会求出:((1,1,1,2))

    为什么会出错?因为要求后代对祖先是大于等于,而不是严格大于。因此,如果有多个值和当前根上的值相同,则当前根取的位置可以向前挪一点。这样,不会改变当前根上的取值,但能把更多更大的值让给其他节点。这里的其他节点,指的是不在当前根的子树内,但编号比当前根子树内的点小的节点。如此调整,可使答案的字典序更大。

    具体来说,设当前根为(u)(从小到大枚举(u),这样才能贪心地保证字典序),其子树大小为( ext{sz}_u)。则要放在(u)上的值,是最大的值(v),满足:(geq v)的、尚未使用的值的数量(geq ext{sz}_u)。然后我们会取走一个(v),同时在(geq v)的值中取走( ext{sz}_u-1)个。但我们此时并不确定,这( ext{sz}_u-1)个数具体要取哪些值。形象地说,我们要在(v)后面,为(u)的子树占一些位置,但由于空位数量可能不止( ext{sz}_u)个,所以暂时不能确定占哪些位置。

    把权值离散化。然后容易想到用线段树维护“取走权值”的操作,用线段树上二分,找到第一个(geq ext{sz}_u)的后缀。一种想法是:线段树每个叶子节点,存一个({0,1})的变量,表示该值是否还未被取走。然后用线段树维护区间和。但是你会发现,这种方法,用于处理:在(v)后面占( ext{sz}_u)个坑,而不确定具体占哪些坑时,是比较棘手的。

    我们需要更巧妙的做法。记(c_v)表示值(v)还剩多少个。设(f_v=sum_{igeq v}c_i)。则我们实际要在线段树上二分的,就是最大的、(f_vgeq ext{sz}_u)的位置(v)。考虑维护(f)数组。在执行“在(v)后面占( ext{sz}_u)个坑”这个操作时,显然的是:(f_{1dots v})都需要减去( ext{sz}_u)。但是大于(v)的值,它们的(f_i)如何变化呢?首先,(f_i)的上限是:(min_{j=1}^{i}f_j)。并且,这个上限总是能够达到的(只要紧挨着(v),取( ext{sz}_u)个数,后面的(f_i)就会达到这个上限)。不妨先对所有(i>v),令(f_i=min_{j=1}^{i}f_j)。然后按编号从小到大,我们会继续处理和(u)同一层的一些节点,然后再进入(u)的子树。此时,有可能与(u)同一层的节点,已经用掉了(v)后面的一些位置,但我们至少能保证(v)后面有( ext{sz}_u)个坑是预留好的。同时,这种让(f_i)顶到“上限”的方式,又能保证在取和(u)同一层的节点时,充分发挥贪心性,尽其所能最大化字典序。

    具体实现时,我们用线段树记录(f)数组,同时维护区间(min)。修改操作是区间加,即对(f_{1dots v})同时加上(- ext{sz}_u)。在线段树上二分位置(v)时,注意到真实的(f)值,其实是表面上这个(f)数组的前缀(min)即可。另外要注意:二分之前,别忘了把(u)的父亲为(u)占的坑补回来。

    时间复杂度(O(nlog n))

    参考代码:

    在LOJ查看

    //problem:LOJ2472
    #include <bits/stdc++.h>
    using namespace std;
    
    #define pb push_back
    #define mk make_pair
    #define lob lower_bound
    #define upb upper_bound
    #define fi first
    #define se second
    #define SZ(x) ((int)(x).size())
    
    typedef unsigned int uint;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int,int> pii;
    
    const int MAXN=5e5;
    int n,a[MAXN+5],fa[MAXN+5],sz[MAXN+5],vals[MAXN+5],cnt_v,c[MAXN+5],ans[MAXN+5];
    double K;
    vector<int>G[MAXN+5];
    bool vis[MAXN+5];
    struct SegmentTree{
    	int mn[MAXN*4+5],tag[MAXN*4+5];
    	//支持区间加
    	//支持线段树上二分:最靠后的一个"前缀min">=x的位置
    	void push_up(int p){
    		mn[p]=min(mn[p<<1],mn[p<<1|1]);
    	}
    	void upd(int p,int v){
    		mn[p]+=v;
    		tag[p]+=v;
    	}
    	void push_down(int p){
    		if(tag[p]){
    			upd(p<<1,tag[p]);
    			upd(p<<1|1,tag[p]);
    			tag[p]=0;
    		}
    	}
    	void build(int p,int l,int r){
    		tag[p]=0;
    		if(l==r){
    			mn[p]=c[l];//>=l的数有c[l]个
    			return;
    		}
    		int mid=(l+r)>>1;
    		build(p<<1,l,mid);
    		build(p<<1|1,mid+1,r);
    		push_up(p);
    	}
    	void range_add(int p,int l,int r,int ql,int qr,int v){
    		if(ql<=l&&qr>=r){
    			upd(p,v);return;
    		}
    		push_down(p);
    		int mid=(l+r)>>1;
    		if(ql<=mid)range_add(p<<1,l,mid,ql,qr,v);
    		if(qr>mid)range_add(p<<1|1,mid+1,r,ql,qr,v);
    		push_up(p);
    	}
    	int query(int p,int l,int r,int x){
    		if(l==r)return mn[p]>=x?l:-1;
    		push_down(p);
    		int mid=(l+r)>>1;
    		if(mn[p<<1]<x){
    			int res=query(p<<1,l,mid,x);
    			push_up(p);
    			return res;
    		}
    		else{
    			int res=query(p<<1|1,mid+1,r,x);
    			push_up(p);
    			return res!=-1?res:mid;
    		}
    	}
    	SegmentTree(){}
    }T;
    void dfs(int u){
    	vis[u]=1;
    	sz[u]=1;
    	for(int i=0;i<SZ(G[u]);++i){
    		int v=G[u][i];
    		dfs(v);
    		sz[u]+=sz[v];
    	}
    }
    int main() {
    	ios::sync_with_stdio(false);
    	cin>>n>>K;
    	for(int i=1;i<=n;++i){
    		fa[i]=floor((double)i/K);
    		if(fa[i]!=0)G[fa[i]].pb(i); 
    		cin>>a[i];
    		vals[i]=a[i];
    	}
    	sort(vals+1,vals+n+1);
    	cnt_v=unique(vals+1,vals+n+1)-(vals+1);
    	for(int i=1;i<=n;++i){
    		a[i]=lob(vals+1,vals+cnt_v+1,a[i])-vals;
    		c[a[i]]++;
    	}
    	for(int i=cnt_v-1;i>=1;--i)c[i]+=c[i+1];
    	T.build(1,1,cnt_v);
    	for(int i=1;i<=n;++i)if(!vis[i])dfs(i);
    	for(int i=1;i<=n;++i){
    		if(fa[i]){
    			T.range_add(1,1,cnt_v,1,ans[fa[i]],sz[i]);
    		}
    		ans[i]=T.query(1,1,cnt_v,sz[i]);
    		assert(ans[i]!=-1);
    		T.range_add(1,1,cnt_v,1,ans[i],-sz[i]);
    	}
    	for(int i=1;i<=n;++i)cout<<vals[ans[i]]<<" 
    "[i==n];
    	return 0;
    }
    
  • 相关阅读:
    Springboot vue 前后分离 跨域 Activiti6 工作流 集成代码生成器 shiro权限
    mybatis3批量更新 批量插入
    Java GC的工作原理详解
    Hadoop的Map侧join
    cut 命令
    head 与 tail
    常用正则
    vim 设置
    Java泛型初探
    linux修改PS1,自定义命令提示符样式
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/12769203.html
Copyright © 2011-2022 走看看