洛谷 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
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目 P5413 "YNOI2019 骑单车" 是一个经典的动态规划和贪心算法的问题。该题主要涉及两个概念:路径规划和状态转移方程。 **背景描述** 假设你在一个二维网格上,每个单元格代表一个地点,你需要从起点出发骑车到终点,并尽可能地减少骑行时间。网格中的每个单元格都有两种可能的状态:平地(速度不变)或斜坡(速度减半)。你的目标是找到一条最短的路线。 **关键点解析** 1. **动态规划**:通常用于求解最优化问题。在这个问题中,我们可以定义一个二维数组 dp[i][j] 表示从起点到位置 (i, j) 的最短行驶时间。状态转移方程会根据当前位置的性质(平地还是斜坡)以及到达此位置的最短路径来自之前的节点计算。 2. **状态转移**:对于平地,dp[i][j] = dp[pi][pj] + cost,表示直接移动到相邻位置的时间;对于斜坡,dp[i][j] = min(dp[pi][pj], dp[pi][pj-1]) + cost/2,因为斜坡速度减半,所以需要选择更早的时刻经过。 3. **贪心策略**:有时候,为了达到全局最优,初始看起来不是最优的选择可能是正确的。但在这个问题中,贪心策略可能并不适用,因为我们不能仅依据当前状态做出决策,需要考虑到整个路径。 4. **边界条件**:初始化 dp 数组时,起点时间设为 0,其余位置设为正无穷大,保证一开始就只会向可达的位置移动。 **代码实现** 实现这样的动态规划算法通常需要用到一个优先队列(如最小堆),以便于高效地查找之前节点的最优时间。 **相关问题--:** 1. 如何设计状态转移方程来处理平地和斜坡的情况? 2. 这个问题是否存在剪枝操作以提高效率? 3. 如果网格大小非常大,如何避免存储所有 dp 值导致的空间爆炸?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值