洛谷 P5397 [Ynoi2018] 天降之物

题目背景

可曾做过这样的梦呢?

在我身边,有一个我从没见过的少女

我很喜欢她,她也很喜欢我

不过——

最后总是

在她被天空掳去的时候

迎来梦醒时分

人?

不对

不是人

没有人会...

长着翅膀

没有没有没有

哪里都没有

没有我可以回去的地方

我没有任何可以回去的地方!

哪里都没有...

我能回去的地方...无论哪里...无论哪里都没有...

我不想杀死任何人,不想伤害任何人!

为什么...为什么要制造出我...

不需要...我不需要这种力量...

幸福不足以形容我现在的心情

我该说什么才好呢

啊对了

我...爱你

我爱你...主人

神话中的伊卡洛斯,由于过于靠近太阳,用蜡粘住的羽毛融化后坠海而死

题目描述

伊卡洛斯给了你一个长为 �n 的序列 �a。

你需要实现 �m 个操作,操作有两种:

  1. 把序列中所有值为 �x 的数的值变成 �y。
  2. 找出一个位置 �i 满足 ��=�ai​=x,找出一个位置 �j 满足 ��=�aj​=y,使得 ∣�−�∣∣i−j∣ 最小,并输出 ∣�−�∣∣i−j∣。

输入格式

第一行两个整数 �,�n,m。

之后一行 �n 个整数,表示序列 �a。

之后 �m 行,每行三个数 ���,�,�opt,x,y。

如果 ���opt 为 11,代表把序列中所有值为 �x 位置的值变成 �y。

如果 ���opt 为 22,代表找出一个位置 �i 满足 ��=�ai​=x,找出一个位置 �j 满足 ��=�aj​=y,使得 ∣�−�∣∣i−j∣ 最小,并输出 ∣�−�∣∣i−j∣,如果找不出这样的位置,输出 Ikaros

本题强制在线,每次的 �,�x,y 需要 xor 上上次答案,如果输出 Ikaros,或者是第一次询问,则上次答案为 00。

共 5050 组数据,数据中保证 �=�n=m。

输出格式

对于每个 22 操作,输出一行一个整数表示答案。

如果无法找出满足题意的 �,�i,j,则输出 Ikaros

输入输出样例

输入 #1复制

5 5
1 2 2 4 4
2 3 3
2 2 4
1 3 2
1 5 5
2 2 5

输出 #1复制

Ikaros
1
1

说明/提示

Idea:nzhtl1477,Solution:nzhtl1477,Code:nzhtl1477,Data:nzhtl1477( partially uploaded )

对于 100%100% 的数据,所有数在 [1,105][1,105] 内,每次操作的值不超过 �n。

题意

  • 给定一个长为�n的序列�a,一共�m个操作:
  • 操作11:把序列里所有值为�x的数改为�y;
  • 操作22:求序列中值为�x的位置和值为�y的位置距离的最小值,如果找不到这样的位置输出Ikaros
  • 强制在线,每次输入的�x和�y需要异或上一次的答案,如果这是第一次输出或上一次输出Ikaros,则不需要异或。
  • 数据范围:1⩽�=�⩽1051⩽n=m⩽105,其他所有数在[1,�][1,n]内。

分析

lxl的毒瘤大分块系列,弑尽破净的第四分块(好中二呀)。

先转换题意:对于每个值都有一个位置集合,支持合并集合(观察题意很容易看出来操作11等价于合并集合),查询两个集合中差值最小的值。

我们先考虑两个暴力做法

  • 维护每一个值的所有位置,修改就把整个位置集合合并,查询就暴力枚举位置。
  • 预处理每一个值离所有其他值的最短距离(这里的预处理可以通过从前到后扫一遍,从后到前扫一遍,做到�(�)O(n)的实现),修改时�(�)O(n)暴力重构,查询�(1)O(1)查询。

但很显然这会时空双爆炸,考虑根号分治来平衡两种暴力的复杂度。

我们先对第一个暴力进行优化,来降低一下复杂度:

我们用vectorvector维护所有的位置,并在其中从小到大排列,合并时可以归并做到�(�)O(n),暴力枚举位置可以用两个指针不断推进,利用这个位置的单调性找出每次最近的两个位置更新答案,也可以做到�(�)O(n)。

设�����sizex​为�x这个值在序列里出现的次数,并规定一个阈值���lim,进行根号分治。

对于每个�x,我们保存一个类似缓存的��vx​(vectorvector型),lxllxl博客里叫它附属集合,�v集合需要保存在上一次重构后所有修改操作中加入的新位置,并通过维护保证它的大小不大于���lim,空间复杂度�(�⋅���)O(n⋅lim)。

对于所有位置集合大于���lim的�x,我们在每一次重构都预处理它里面每个值离其他值的最短距离,设为����,�ansx,y​(位置集合�x离值�y的最短距离),可以发现这样的�x不超过����limn​个,因此我们的空间复杂度为�(�⋅���)O(n⋅lim)。

现在讲一讲具体操作:

对于修改(注意,这里我们可以进行一定的操作将�x与�y交换,因此不妨设�����⩽�����sizex​⩽sizey​)

  • 当�����⩽���,�����⩽���sizex​⩽lim,sizey​⩽lim时
    • 若�����+�����⩽���sizex​+sizey​⩽lim,我们就暴力合并�x和�y的�v集合,每一次复杂度为�(���)O(lim),总复杂度为�(�⋅���)O(m⋅lim)。
    • 若�����+�����>���sizex​+sizey​>lim,那么我们预处理���ans,并清空�v集合,很容易发现这样的操作不超过����limn​次,而每一次预处理���ans是�(�)O(n),因此总复杂度是�(�2���)O(limn2​)。
  • 当�����⩽���,�����>���sizex​⩽lim,sizey​>lim时
    • 若�����+�[�].����()⩽���sizex​+v[y].size()⩽lim,我们暴力把�x加入到�y的�v集合中,总复杂度�(�⋅���)O(m⋅lim)。
    • 若�����+�[�].����()>���sizex​+v[y].size()>lim,重构�y的���ans集合,加入�x和�[�]v[y]的贡献,并清空�y的�v集合,操作数显然不超过����limn​,复杂度为�(�2���)O(limn2​)。
  • 当�����>���,�����>���sizex​>lim,sizey​>lim时,直接暴力将�x的所有位置合并到�y上面,单次复杂度�(�)O(n),总复杂度�(�2���)O(limn2​),理由同上。

对于查询(查询的�x和�y顺序不影响答案,因此设�����⩽�����sizex​⩽sizey​)

  • 当�����⩽���,�����⩽���sizex​⩽lim,sizey​⩽lim时,我们采用上述暴力11来计算答案,复杂度为�(���)O(lim),总复杂度�(�⋅���)O(m⋅lim)。
  • 当�����⩽���,�����>���sizex​⩽lim,sizey​>lim时,采用上述暴力计算�x的位置集合和�y的�v集合的答案,同时别忘了加上���[�]ans[y]的贡献,总复杂度�(�⋅���)O(m⋅lim)。
  • 当�����>���,�����>���sizex​>lim,sizey​>lim时,采用上述暴力计算�x和�y的�v集合的答案,加上���[�]ans[x]和���[�]ans[y]的贡献,总复杂度�(�⋅���)O(m⋅lim)。

故程序的时间复杂度为�(�2���+�⋅���)O(limn2​+m⋅lim),空间复杂度为�(�⋅���)O(n⋅lim),因为�=�n=m,所以���=�lim=n​的复杂度最优,时间复杂度�(��)O(nn​),空间复杂度�(��)O(nn​)。

代码如下:

#include<stdio.h>
#include<vector>
#include<string.h>
#include<math.h>
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=1000005,maxt=505;
int n,m,lastans,lim,tot;
int a[maxn],val[maxn],size[maxn],id[maxn],ans[maxt][maxn];
vector<int>v[maxn];
inline int abs(int x){
	return x<0? -x:x;
}
void build(int x){//重构块
	int dis;
	if(id[x]==0)
		id[x]=++tot;
	memset(ans[id[x]],0x3f,sizeof(ans[id[x]]));
	v[x].clear();
	dis=inf;
	for(int i=1;i<=n;i++){
		if(a[i]==x)
			dis=0;
		else dis++;
		ans[id[x]][a[i]]=min(ans[id[x]][a[i]],dis);
	}
	dis=inf;
	for(int i=n;i>=1;i--){
		if(a[i]==x)
			dis=0;
		else dis++;
		ans[id[x]][a[i]]=min(ans[id[x]][a[i]],dis);
	}
}
void init(){//初始化
	lim=sqrt(n);
	for(int i=1;i<=n;i++)
		val[i]=n+1;
	for(int i=1;i<=n;i++)
		size[a[i]]++,v[a[i]].push_back(i),val[a[i]]=a[i];
	for(int i=1;i<=n;i++)
		if(size[i]>lim)
			build(i);
}
void merge(int x,int y){//合并两个附属集合
	vector<int>res;
	for(int i=0,j=0;i<v[x].size()||j<v[y].size();){
		if(j>=v[y].size()||(i<v[x].size()&&v[x][i]<v[y][j]))
			res.push_back(v[x][i]),i++;
		else res.push_back(v[y][j]),j++;
	}
	v[y]=res;
}
void updateA(int x,int y){//暴力1
	for(int i=0;i<v[x].size();i++)
		a[v[x][i]]=y;
	for(int i=1;i<=tot;i++)
		ans[i][y]=min(ans[i][y],ans[i][x]);
	merge(x,y);
}
void updateB(int x,int y){//暴力2
	for(int i=1;i<=n;i++)
		if(a[i]==x)
			a[i]=y;
	build(y);
}
void update1(int x,int y){//修改-分类讨论1
	if(size[x]+size[y]<=lim)
		updateA(x,y);
	else updateB(x,y);
}
void update2(int x,int y){//修改-分类讨论2
	if(size[x]+v[y].size()<=lim)
		updateA(x,y);
	else updateB(x,y);
}
void update3(int x,int y){//修改-分类讨论3
	updateB(x,y);
}
void update(int x,int y){//修改
	if(size[val[x]]==0||val[x]==val[y])
		return ;
	int px=val[x],py=val[y];
	if(size[val[x]]>size[val[y]])
		val[y]=val[x],swap(px,py);
	val[x]=n+1,x=px,y=py;
	if(x==n+1||y==n+1)
		return ;
	if(size[x]<=lim&&size[y]<=lim)
		update1(x,y);
	if(size[x]<=lim&&size[y]>lim)
		update2(x,y);
	if(size[x]>lim&&size[y]>lim)
		update3(x,y);
	size[y]+=size[x],size[x]=0;
	v[x].clear();
}
int calc(int x,int y){//合并两个块的附属集合
	int i=0,j=0,res=inf;
	if(size[x]==0||size[y]==0)
		return inf;
	while(i<v[x].size()&&j<v[y].size()){
		if(v[x][i]<v[y][j])
			res=min(res,v[y][j]-v[x][i]),i++;
		else res=min(res,v[x][i]-v[y][j]),j++;
	}
	return res;
}
int query1(int x,int y){//查询-分类讨论1
	return calc(x,y);
}
int query2(int x,int y){//查询-分类讨论2
	return min(ans[id[y]][x],calc(x,y));
}
int query3(int x,int y){//查询-分类讨论3
	return min(min(ans[id[x]][y],ans[id[y]][x]),calc(x,y));
}
int query(int x,int y){//查询
	x=val[x],y=val[y];
	if(x==n+1||y==n+1||size[x]==0||size[y]==0)
		return -1;
	if(x==y)
		return 0;
	if(size[x]>size[y])
		swap(x,y);
	if(size[x]<=lim&&size[y]<=lim)
		return query1(x,y);
	if(size[x]<=lim&&size[y]>lim)
		return query2(x,y);
	if(size[x]>lim&&size[y]>lim)
		return query3(x,y);
	return -1;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	init();
	for(int i=1;i<=m;i++){
		int t,x,y;
		scanf("%d%d%d",&t,&x,&y);
		x^=lastans,y^=lastans;
		if(t==1)
			update(x,y);
		if(t==2){
			int res=query(x,y);
			if(res==-1)
				lastans=0,puts("Ikaros");
			else lastans=res,printf("%d\n",res);
		}
	}
	return 0;
}

拜拜! 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值