• [九省联考2018]IIIDX


    XXXIV.[九省联考2018]IIIDX

    首先,一个非常naive的想法是,建出通关的树出来,然后dfs它,在访问到一个节点时,将现有最小的值赋给它,然后从大到小遍历每个子节点。

    这个算法会被 \(d\) 相同的情形叉掉,因为它可以构造出这样一组数据:若某个节点的子树为 \(1\),且它的兄长(指与它有同一父亲,且权值小于它的最大节点)的子树不为 \(1\),且它的值和兄长的值相同。这时,我们可以掏出兄长的一个儿子,将其与该节点交换,并发现树仍然合法,但是字典序变大了。

    于是我们开始思考这个问题的本质。

    我们发现,用任意一组点集覆盖任意一棵子树,都能保证产生一组解。于是我们萌生了一个念头,能不能从小到大确定每个数的值,且使得剩余的数存在一种合法解?显然这种方法是一定正确的。

    我们发现,假如一个点 \(x\) 的子树大小是 \(sz_x\),且赋值为 \(b_i\),则其子树中每个点的权值都 \(\geq b_i\)。这就意味着,对于 \(x\) 的子树,我们需要恰好 \(sz_x\)\(\geq b_i\) 的节点来填满这棵子树。并且,依照我们上面的发现,这是充要条件。

    于是问题转换为找到最大的 \(b_i\) 使得存在上述点集。

    我们考虑用一棵线段树维护,线段树上每个下标 \(u\) 存了 \(\geq u\) 的尚未被动用的权值数。在确定一个点的权值为 \(b_i\) 后,我们需要对于 \(\leq b_i\) 的所有位置全都减去 \(sz_i\),因为这其中每个位置都确定可用的权值减少了 \(sz_i\)。但是这并不意味着 \(>b_i\) 的权值数就不会变化,因为谁又知道子树中到底选了哪些数呢?

    因此,在确定一个 \(b_i\) 时,我们不能简单地找到最大的权值数 \(\geq sz_i\) 的下标,而应找到对于所有 \(v\leq u\),都有 \(v\) 的权值数 \(\geq sz_i\) 的最大 \(u\)。这是很显然的。

    而这是线段树二分的常规操作。随便写写就行了。

    需要注意的是,确定 \(b_i\) 时的操作的本质是预订,这就意味着当操作真正进行到儿子时,需要把父亲预订的那些位置给退回,同时注意一个父亲只能被退回一次。

    时间复杂度 \(O(n\log n)\)

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    int n,m,fa[500100],sz[500100],a[500100],b[500100];
    double k;
    vector<int>v;
    #define lson x<<1
    #define rson x<<1|1
    #define mid ((l+r)>>1)
    struct SegTree{int tag,mn;}seg[2001000];
    void pushup(int x){seg[x].mn=min(seg[lson].mn,seg[rson].mn);}
    void ADD(int x,int y){seg[x].mn+=y,seg[x].tag+=y;}
    void pushdown(int x){ADD(lson,seg[x].tag),ADD(rson,seg[x].tag),seg[x].tag=0;}
    void modify(int x,int l,int r,int P,int val){
    	if(l>P)return;
    	if(r<=P){ADD(x,val);return;}
    	pushdown(x),modify(lson,l,mid,P,val),modify(rson,mid+1,r,P,val),pushup(x);
    }
    int innersearch(int x,int l,int r,int val){
    	if(l==r)return seg[x].mn>=val?l:l-1;
    	pushdown(x);
    	if(seg[lson].mn>=val)return innersearch(rson,mid+1,r,val);
    	else return innersearch(lson,l,mid,val);
    }
    int outersearch(int x,int l,int r,int P,int val){
    //	printf("%d[%d,%d]:%d,%d\n",x,l,r,P,val);
    	if(r<P)return -1;
    	if(l>=P){
    //		printf("[%d,%d]:%d %d\n",l,r,val,seg[x].mn);
    		if(seg[x].mn>=val)return -1;
    		return innersearch(x,l,r,val);
    	}
    	pushdown(x);
    	int tmp=outersearch(lson,l,mid,P,val);
    	if(tmp!=-1)return tmp;
    	return outersearch(rson,mid+1,r,P,val);
    }
    bool vis[500100];
    void iterate(int x,int l,int r){
    	if(l==r)printf("%d ",seg[x].mn);
    	else pushdown(x),iterate(lson,l,mid),iterate(rson,mid+1,r);
    }
    int main(){
    	scanf("%d%lf",&n,&k);
    	for(int i=n,x;i;i--)sz[fa[i]=i/k]+=++sz[i],scanf("%d",&a[i]),v.push_back(a[i]);
    	sort(v.begin(),v.end()),v.resize(m=unique(v.begin(),v.end())-v.begin());
    	for(int i=1;i<=n;i++)a[i]=lower_bound(v.begin(),v.end(),a[i])-v.begin()+1,modify(1,1,m,a[i],1);
    //	for(int i=1;i<=n;i++)printf("%d %d\n",i,fa[i]);
    //	for(int i=1;i<=n;i++)printf("%d ",sz[i]);puts("");
    //	iterate(1,1,m);puts("");
    	vis[0]=true,b[0]=1;
    	for(int i=1;i<=n;i++){
    		if(!vis[fa[i]])modify(1,1,m,b[fa[i]],sz[fa[i]]-1),vis[fa[i]]=true;
    //		puts("");
    //		iterate(1,1,m);puts("");
    		b[i]=outersearch(1,1,m,b[fa[i]],sz[i]);
    		if(b[i]==-1)b[i]=m;
    //		printf("%d:%d\n",sz[i],b[i]);
    		modify(1,1,m,b[i],-sz[i]);
    //		iterate(1,1,m);puts("");
    	}
    	for(int i=1;i<=n;i++)printf("%d ",v[b[i]-1]);
    	return 0;
    }
    

  • 相关阅读:
    每周总结8.18
    每周总结7.28
    每周总结8.25
    每周总结7.21
    每周总结8.11
    每周总结8.4
    大道至简 读后感
    递归进行回文的判断
    课后作业1
    GoodBlogs Websites
  • 原文地址:https://www.cnblogs.com/Troverld/p/14611627.html
Copyright © 2020-2023  润新知