[Ynoi2018]天降之物

Posted StaroForgin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Ynoi2018]天降之物相关的知识,希望对你有一定的参考价值。

天降之物

题解

只有 ** 出题人才会出这种又难调又卡常的 ** 题。

笔者原先很快奶出了一个 O ( n n log ⁡   n ) O\\left(n\\sqrt{n}\\log\\,n\\right) O(nn logn)的做法,然后调了一个下午,交上去后死活过不了,只好又优化了半天优化出了一个 O ( n n ) O\\left(n\\sqrt{n}\\right) O(nn ),又调了一个晚上,最后还调了半天块长才过。

首先,看到题面题目名我们应该很容易想到分块,当然,这里准确说应该叫根号分治。
对于每个不同的数,我们都维护一个有序数组,我们的修改操作相当于合并两个有序数组。
如果合并的两个数组都小于 S S S,直接像归并排序一样用指针合并就行了。
如果合并的两个数组都大于 S S S,由于合并是具有永久性的,这种合并当然也是有限的,不会超过 n S \\frac{n}{S} Sn次,我们也可以像上面那样暴力合并。
但如果我们合并的两个数组一个大于 S S S,一个小于 S S S呢,这样显然是不能像刚刚那样合并呢。
但是这种一大一小的合并很容易让我们联想到启发式合并,将小的插入到大的里面去。
如何较快地在一个有序数列中插入一个数,明显就是一个 s e t set set的板子嘛。
所以我们可以对每个数都维护一个 s e t set set,前两种操作可以暴力重构 s e t set set,而下面这种就直接将小的 s e t set set一个一个插入到大的里面去即可。

既然是根号分治,我们自然想到需要维护大于 S S S的数组之间的答案,由于合并是永久的,我们大于 S S S的数组也只会增多,或者被合并,不会凭空消失。
所以,当一个数组变得大于 S S S时,我们可以 O ( n ) O\\left(n\\right) O(n)地将整个序列扫一遍,维护它与其它数组间的答案,这个只需要从左到右一次和从右到左一次,分别记录下距离每个点最近的属于该数组的点即可。
当两个大于 S S S的数组合并时,我们只需将它们的答案暴力合并即可。
当一个较小的数组插入到另外一个大于 S S S数组里面去时,我们同样可以对于插入的每一个点维护它与其它数字的距离。
由于每一个数只会插入到一个大于 S S S的数组一次,所以每个数自然也只会维护一次。
同样,如果求答案时是两个大于 S S S的数组,我们直接将我们记录的答案输出即可,如果存在小于 S S S的数组,我们可以就对于这个小数组的每个数看看它与另外一个的距离。

至于维护序列中的每个数现在应该是什么,我们可以采用并查集。
每次将 x x x连到 y y y上面后我们都新开一个节点,表示现在值为 x x x,先前值为 x x x的节点都连到 y y y上去了,而之后还会有其它值变成 x x x

容易发现,上面这种做法是时间复杂度是 O ( ( n − x ) n S + x n − x S l o g   S + n S l o g   S ) ⩾ O ( n n l o g   n ) O\\left(\\frac{(n-x)n}{S}+x\\frac{n-x}{S}log\\,S+nSlog\\,S\\right)\\geqslant O\\left(n\\sqrt{n}log\\,n\\right) O(S(nx)n+xSnxlogS+nSlogS)O(nn logn)
然而在lxl的毒瘤卡常下,这种方法当然过不了,我们需要想个办法把 log ⁡   n \\log\\,n logn去掉显然不可能是去掉 n \\sqrt{n} n

既然要去掉 log ⁡ n \\log n logn,我们当然就不能用 s e t set set了,我们考虑使用 v e c t o r vector vector来维护有序数组。
v e c t o r vector vector并不支持我们很快的将一个较小的数组合并到较大的数组里面去,我们不妨开两个 v e c t o r vector vector,一个维护较大的数组,一个维护较小的数组,分别计算答案,当小的数组大小达到 S S S时,再将其合并进去。
同样可以发现,这种合并的次数也是有限的·。
但这并不能消灭掉我们询问时查询小数组与大数组之间的 log ⁡   n \\log\\,n logn,这也就意味着我们需要维护大数组与每个数组之间的距离,这个改变一次就 O ( n ) O\\left(n\\right) O(n)了。
但事实上这个的改变次数是极少的,它只会在有两个大数组合并,或小数组插入到大数组时改变,其它小数组与小数组合并时只会枚举改变 n S \\frac{n}{S} Sn个大数组。
既然都把这么多维护了,我们自然也就不需要再将大数组保留下来的,反正我们也不会用它求答案,所以我们只需要记录下每个大数组与其它数组的距离,根本不需要维护大数组。
而拥有大数组的小数组自身与其它数组的距离我们是并不需要维护的,我们只需要维护它与别的大数组之间的距离,这可以直接在有其它数组与小数组合并时加到大数组里面去。
它与别的小数组的距离可以在求答案时 O ( S ) O\\left(S\\right) O(S)地求出。
就像归并一样,我们每次记录将这个点放下去时与别的点之间最近的距离即可。
而其它的几种答案我们都是已经维护了的,直接加入即可。

这样,我们就将我们的时间复杂度优化到了 O ( n n ) O\\left(n\\sqrt{n}\\right) O(nn )了,可以轻松通过然而我还是卡了半天块长

源码

唔姆,跑得其实挺快的,还在第一页。

#include<bits/stdc++.h>
using namespace std;
#define MAXN 100005
#define lowbit(x) (x&-x)
#define reg register
#define pb push_back
#define mkpr make_pair
#define lson (rt<<1)
#define rson (rt<<1|1)
typedef long long LL;
typedef unsigned long long uLL;
const int INF=0x3f3f3f3f;
const int mo=1e9;
const int inv2=499122177;
const int jzm=2333;
const int lim=100000;
const int n1=1200;
const int orG=3,invG=332748118;
const double Pi=acos(-1.0);
const double eps=1e-7;
typedef pair<int,int> pii;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
template<typename _T>
void print(_T x){if(x<0){x=(~x)+1;putchar('-');}if(x>9)print(x/10);putchar(x%10+'0');}
LL gcd(LL a,LL b){return !b?a:gcd(b,a%b);}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1LL)t=1ll*a*t%p;a=1ll*a*a%p;s>>=1LL;}return t;}
const int M=lim/n1+5;
int n,m,id[MAXN],dep,pid[M],pre[MAXN],a[MAXN],dp[MAXN][M];
int idx,cnt,lastans,fa[2*MAXN],ip[2*MAXN],tot,bel[MAXN];
vector<int>fir[MAXN];
vector<int>::iterator it;
void makeSet(int x){for(int i=1;i<=x;i++)fa[i]=ip[i]=bel[i]=i;}
int findSet(int x){return fa[x]==x?x:fa[x]=findSet(fa[x]);}
vector<int> merge(int x,int y){
	vector<int>res;res.clear();
	int siza=fir[x].size(),sizb=fir[y].size(),i=0,j=0;
	for(;i<siza&&j<sizb;)
		if(fir[x][i]<fir[y][j])res.push_back(fir[x][i]),i++;
		else res.push_back(fir[y][j]),j++;;
	for(;i<siza;i++)res.push_back(fir[x][i]);
	for(;j<sizb;j++)res.push_back(fir[y][j]);
	return res;
}
int query(int x,int y){
	int siza=fir[x]以上是关于[Ynoi2018]天降之物的主要内容,如果未能解决你的问题,请参考以下文章

咕掉的题目

2020每日学习8h打卡

2020每日学习8h打卡

[Ynoi2018]五彩斑斓的世界

题解 P4117 [Ynoi2018]五彩斑斓的世界

[Ynoi2018]未来日记 - 题解