zoukankan      html  css  js  c++  java
  • 【洛谷4364】[九省联考2018] IIIDX(线段树)

    点此看题面

    • 给定一个长度为(n)的序列以及一个实数参数(k)
    • 要求将序列重新排序,满足(forall ige k,a_ige a_{lfloorfrac ik floor}),且字典序最大。
    • (nle5 imes10^5)

    一个简单但错误的贪心

    先把题意转化,我们可以建出一棵树形结构,那么就是要让每个点的权值小于等于子树内所有点的权值。

    然后就很容易想到一个贪心,对于每个点(x),假设其子树大小为(Sz_x),就为它子树内的点预留(Sz_x-1)个较大值,将第(Sz_x)大的数作为当前点的权值。

    的确,在没有相同值的时候,这个做法的正确性是显然的,而良心出题人也给了这种贪心(55)分。

    但是啊,有了相同值就不一定了。

    随便给个例子,当(n=k=3),四个数分别为(2,1,1)的时候,显然(1)号点只能填(1),而按照前面的贪心更大的(2)会填给(3)号点,但实际上填给(2)号点更优且依然能满足条件。

    因此这个做法需要修正。

    线段树上二分

    依旧考虑前面的例子,发现贪心能够保证我们选择当前点的正确性,但可能影响到和当前点同一深度的其他点的选择。

    为此,首先我们依旧求出第(Sz_x)大的数,(x)的答案的确是这个值,但我们并不一定要选择第(Sz_x)个位置上的这个值,而应该选择尽可能靠后的这个值来使答案更优(具体实现中可以直接记录每种值最右边的位置(R_v))。

    这样一来尽管还是贪心,就不像先前那样简单了,而是要利用线段树上二分来求解。

    具体地,线段树上维护每个位置的排名,一次询问就是要找出线段树上排名大于等于(Sz_x)的最靠左的数,也就是排名小于(Sz_x)的最靠右的数所在位置加(1)

    找到一个位置后需要在前面预留出(Sz_x-1)个位置,这直接在线段树上先区间减法修改,等到处理到子树内之后再区间加法还原回去即可。

    代码:(O(nlogn))

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 500000
    using namespace std;
    int n,a[N+5],dc,dv[N+5],R[N+5],f[N+5],sz[N+5],ans[N+5];double k;
    I bool cmp(CI x,CI y) {return x>y;}
    class SegmentTree
    {
    	private:
    		#define PT CI l=0,CI r=n,CI rt=1
    		#define LT l,mid,rt<<1
    		#define RT mid+1,r,rt<<1|1
    		#define PU(x) (V[x]=min(V[x<<1],V[x<<1|1]))
    		#define PD(x) (F[x]&&(T(x<<1,F[x]),T(x<<1|1,F[x]),F[x]=0))
    		#define T(x,v) (V[x]+=v,F[x]+=v)
    		int V[N<<2],F[N<<2];
    	public:
    		I void Build(PT)//建树
    		{
    			if(l==r) return (void)(V[rt]=l);RI mid=l+r>>1;Build(LT),Build(RT),PU(rt);
    		}
    		I int Q(CI x,PT)//线段树上二分大于等于x的最小位置,先找到小于等于x的最大位置再加1
    		{
    			if(l==r) return l+1;RI mid=l+r>>1;PD(rt);return V[rt<<1|1]<x?Q(x,RT):Q(x,LT);
    		}
    		I void U(CI L,CI R,CI v,PT)//区间修改
    		{
    			if(L<=l&&r<=R) return (void)T(rt,v);RI mid=l+r>>1;PD(rt);
    			L<=mid&&(U(L,R,v,LT),0),R>mid&&(U(L,R,v,RT),0),PU(rt);
    		}
    }S;
    int main()
    {
    	RI i,x;for(scanf("%d%lf",&n,&k),i=1;i<=n;++i) scanf("%d",a+i);
    	for(i=n;i;--i) sz[f[i]=i/k]+=++sz[i];sort(a+1,a+n+1,cmp),S.Build();//求出每个点子树大小;权值从大到小排序后建线段树
    	for(i=1;i<=n;++i) (!dc||dv[dc]^a[i])&&(dv[++dc]=a[i]),R[a[i]=dc]=i;//离散化,记录每个值最右边的位置
    	for(i=1;i<=n;++i) f[i]^f[i-1]&&(S.U(ans[f[i]],n,sz[f[i]]-1),0),//把预留位置还原回去
    		printf("%d ",dv[x=a[S.Q(sz[i])]]),S.U(ans[i]=R[x]--,n,-sz[i]);return 0;//相同值尽可能靠后选择,給子树预留
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    证明最大公约数Stein算法(高精度算法)
    链表常用内容和易犯错误
    斐波那契数列——各种公式证明
    用矩阵和待定系数法求数列的分析(复杂度log(n))
    盒模型(外边距)
    盒子模型(内边距)
    盒子模型(边框)
    jupyterhub
    1分钟k线图能反映什么?(转)
    python的self
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu4364.html
Copyright © 2011-2022 走看看