码迷,mamicode.com
首页 > 其他好文 > 详细

[九省联考2018] IIIDX

时间:2021-04-09 12:49:45      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:最大   ret   code   大小   upd   lan   iii   const   一个   

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

一、题目

点此看题

二、解法

首先我观察了一下数据范围,\(d_i\) 互不相同的分有 \(50\%\),先指着这个想一想。可以把 \(i\)\(\lfloor\frac{i}{k}\rfloor\) 连一条边,那么构成的图一定是一颗树,\(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\) 不同的时候显然不成立,因为当前点可以会面临很多相同值得选择,而我们不知道给子树划分什么值域区间是最优的,所以就 \(\tt Wa\) 掉了。

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

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

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

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

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

时间复杂度 \(O(n\log 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]]);
}

[九省联考2018] IIIDX

标签:最大   ret   code   大小   upd   lan   iii   const   一个   

原文地址:https://www.cnblogs.com/C202044zxy/p/14631633.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!