zoukankan      html  css  js  c++  java
  • [九省联考2018] IIIDX

    人类智慧题,我觉得网上的题解大多讲的不清楚(导致我看了很久),我尽量把你讲懂。

    一、题目

    点此看题

    二、解法

    首先我观察了一下数据范围,(d_i) 互不相同的分有 (50\%),先指着这个想一想。可以把 (i)(lfloorfrac{i}{k} floor) 连一条边,那么构成的图一定是一颗树,(0) 是这棵树的根。

    那么我们从 (0) 开始遍历,每次把当前点的儿子排个序,优先走编号小的儿子,我们维护一个能用的值域区间,走儿子的时候保证子树能合法的情况下取最大值即可,实际上能得到六十分,关键代码:

    sort(g.begin(),g.end());
    for(int i=0;i<g.size();i++)
    {
    	int t=siz[g[i]];
    	ans[g[i]]=a[r-t+1];
    	dfs(g[i],r-t+1,r);
    	r-=t;
    }
    

    这个方法在 (d_i) 不同的时候显然不成立,因为当前点可以会面临很多相同值得选择,而我们不知道给子树划分什么值域区间是最优的,所以就 ( t Wa) 掉了。

    我们只能知道子树取值的一个模糊的范围,形如:值大于x的数中要选出y个数,既然现在确定不了就暂时放一放,但是要把这个限制表示出来。所以我们要给子树预留若干个合法的取值

    这个预留操作看上去就很不可做,实际上是可以维护的。把值从大到小排序对于每一个位置都维护一个标记数组 (f[i]) 来表示这个预留的限制,(f[i]) 就从 (i) 往左一个一个选(剩下的在越左边越好),那么加上预留的限制就是把 (igeq x)(f[i]) 减去 (y),想要求某个位置 (x) 之前最多能放的个数就求 (min_{igeq x}f[i])

    维护标记数组是 (O(n^2)) 的,但是你发现所有位置都能共用一个标记数组,观察标记数组的特点可以想到那线段树去维护他,这种方法不只适用此题,对于模糊限制的表示有借鉴意义,我暂且称之为:最小值差分(我觉得比较形象了)

    表示出了预留的限制就不难设计本题的算法了,我们按编号大小访问每个点,先把父亲的预留操作回撤(因为已经到子树了),然后在线段树上二分可以找到最左边的 (x),这个点的答案就是 (x) 了。然后找到和 (x) 同权值的最右边的 (p)(注意如果 (p) 已经被选要继续右移),更新一下标记数组来预留子树的取值。

    线段树二分的时候如果右子树的最小值不合法就必须往右子树走,否则就无脑走左子树,但是到最底层的需要判一下当前点合不合法,这是由我们在线段树上的走法决定的,如果不合法需要右移一格。

    时间复杂度 (O(nlog n)),我的代码是网上广为流传的写法。

    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int M = 500005;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,a[M],cnt[M],siz[M],fa[M],ans[M],vis[M],s[4*M],tag[4*M];
    int cmp(int a,int b)
    {
    	return a>b;
    }
    void up(int i)
    {
    	s[i]=min(s[i<<1],s[i<<1|1]);
    }
    void down(int i)
    {
    	if(tag[i])
    	{
    		s[i<<1]+=tag[i];
    		tag[i<<1]+=tag[i];
    		s[i<<1|1]+=tag[i];
    		tag[i<<1|1]+=tag[i];
    		tag[i]=0;
    	}
    }
    void build(int i,int l,int r)
    {
    	if(l==r)
    	{
    		s[i]=l;
    		return ;
    	}
    	int mid=(l+r)>>1;
    	build(i<<1,l,mid);
    	build(i<<1|1,mid+1,r);
    	up(i);
    }
    void upd(int i,int l,int r,int L,int R,int f)
    {
    	if(L>r || l>R) return ;
    	if(L<=l && r<=R)
    	{
    		s[i]+=f;
    		tag[i]+=f;
    		return ;
    	}
    	down(i);
    	int mid=(l+r)>>1;
    	upd(i<<1,l,mid,L,R,f);
    	upd(i<<1|1,mid+1,r,L,R,f);
    	up(i);
    }
    int ask(int i,int l,int r,int x)
    {
    	if(l==r) return s[i]>=x?l:l+1;
    	down(i);
    	int mid=(l+r)>>1;
    	if(s[i<<1|1]<x) return ask(i<<1|1,mid+1,r,x);
    	return ask(i<<1,l,mid,x);
    }
    signed main()
    {
    	n=read();double k;
    	scanf("%lf",&k);
    	for(int i=1;i<=n;i++)
    	{
    		siz[i]=1;
    		a[i]=read();
    	}
    	sort(a+1,a+n+1,cmp);
    	build(1,1,n);
    	for(int i=n;i>=1;i--)
    	{
    		int t=i/k;
    		fa[i]=t;
    		if(a[i]==a[i+1]) cnt[i]=cnt[i+1]+1;
    		siz[t]+=siz[i];
    	}
    	for(int i=1;i<=n;i++)
    	{
    		if(fa[i] && !vis[fa[i]])
    		{
    			upd(1,1,n,ans[fa[i]],n,siz[fa[i]]-1);
    			vis[fa[i]]=1;
    		}
    		int x=ask(1,1,n,siz[i]);
    		x+=cnt[x];cnt[x]++;
    		ans[i]=x;
    		upd(1,1,n,x,n,-siz[i]);
    	}
    	for(int i=1;i<=n;i++)
    		printf("%d ",a[ans[i]]);
    }
    
  • 相关阅读:
    iOS开发_当一个控件被添加到父控件中会调用
    iOS_判断应用在前台还是后台
    iOS开发_ SDWebImage先下载图片保存起来,需要时再调用
    WARNING ITMS90901: "Missing fullscreen support for the latest iPad mini display.
    iOS开发_显示带HTML标签的富文本
    iOS开发_WKWebView隐藏滚动条
    iOS_获取应用当前定位授权状态
    iOS开发_判断字符串是否为空的处理
    UIAlertController和UIActivityViewController在ipad中的兼容性问题
    npm run serve报错提示js堆内存不足
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/14631633.html
Copyright © 2011-2022 走看看