luogu P9474 [yLOI2022] 长安幻世绘 详细题解

这篇具有很好参考价值的文章主要介绍了luogu P9474 [yLOI2022] 长安幻世绘 详细题解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

原题:P9474 [yLOI2022] 长安幻世绘

看到很多大佬的题解直接讲了做法,本蒟蒻看得不是很懂,调了很久才把这题做出来,于是写了这篇比较详细的题解谈一下我做这题从头到尾的思路,希望对各位有帮助 qwq。

思路

我们首先思考这样一个问题,如果已经知道最终答案对应的最大值和最小值,又不知道题目给的 \(m\),该如何去求这样选出的子列的长度呢?

显然,我们要让原序列中大小在最大值和最小值之间的所有数都尽可能多地选进子列(因为如果有可以选的但没有选完,就可以把选的子列中最小值或最大值替换中间那些没选的,这样会得到一种极差更小的情况,与条件矛盾),但是这些数在原序列中可能是连续的,不能都选进去,于是分别考虑这些可以选的数在原序列中构成的每一个最长连续段,容易发现,只有隔一个选一个才能选的尽可能多,如果段长是偶数,最多选一半,段长是奇数,首尾都选了,最多选段长加 \(1\) 的一半。实际上,最多选的数的个数就是段长除以 \(2\) 并向上取整,而不同段选的数互不干扰,所以当前子列的长度就是每段可选的最多个数加起来

这样就很容易想到一种暴力枚举的方法,我们只需要枚举所有的最大值和最小值,其中必然有一个组合对应着最终答案,每次 \(O(n)\) 算出选出的子列长度,在所有长度为 \(m\) 的子列中找极差最小的即可,时间复杂度为 \(O(n^3)\)

考虑在暴力枚举的基础上进行优化,可以发现,有大量的情况找到的子列长度都不为 \(m\),不会影响答案,浪费了很多时间,如何尽量让枚举的情况长度都是 \(m\) 呢?由于我们枚举的是最大值和最小值,是有大小关系的,尝试把原数列从小到大排序,形成一个有单调性的序列,这样就可以使用双指针去枚举最大值和最小值了,用左指针枚举最小值,右指针枚举最大值,可以发现左指针向右移动的时候右指针一定不可能向左移动(因为左指针让子列长度变小或不变,为了让找到的子列长度更接近 \(m\),右指针只能向右移或不动,向左移就会得到一个更小的子列长度,对答案一定是没有意义的),满足双指针法两个指针都单调不减的性质,这样右指针和左指针移动次数都最多只有 \(n\) 次,减少了暴力方法中大量没有意义的枚举次数。

但是 \(O(n^2)\) 的复杂度仍然不能通过此题,我们便着眼于子段长度的求法上,朴素方法需要每次遍历整个原序列来找到每一个最长连续段的长度,但我们发现在双指针的枚举过程中,当前枚举的子列每次只会有一个数发生变化,左指针移动则代表原数列在当前区间内可以选的数会少一个,右指针移动则会增加一个,而其他的数都是不变且有序的。所以便想到用一种数据结构去维护当前在原数列中可以选的数(不是当前子列),这里以线段树为例,每次增加一个数和减少一个数都是标准的单点修改,而要得到每次选出的子列长度则稍微有些难度,我们可以只考虑每次单点修改后子列长度的变化:如果新增了一个可以选的数,那么子列长度的变化只与这个数在原数列中左右相邻的最长连续段有关,分以下 \(3\) 种情况讨论:

  1. 如果左右都没有相邻的最长连续段(即左右两数都不在当前可以选的数范围内),则原序列中会多一个长度为 \(1\) 的最长连续段,选的子列长度会多 \(1\)

  2. 如果仅有一侧有相邻的最长连续段,则只会受这个相邻的最长连续段影响,显然,如果其长度为偶数,可以在以前的基础上多选一个,子列长度多 \(1\),奇数则不行,子列长度不受影响。

  3. 如果两侧都有相邻的最长连续段,则只有在两侧最长连续段长度都为偶数的时候可以多选这个数,否则子列长度不受影响。

而对于删除(即减少了一个可以选的数)的情况,实际上和新增是一样的,删除相当于是去除原来新增这个数对子列长度的影响,与上面的判断方法相同,在新增是需要增加子列长度时,减少子列长度即可(即与新增的操作相反)。

而如何找到左右相邻的最长连续段呢?使用线段树维护每个区间内可选数的最长前缀和最长后缀,由线段树的区间可合并性质,递归将数列第一个数当前数的左边一个数的区间合并,新区间的最长后缀就是左边相邻的最长连续段,而递归将当前数的右边一个数到数列最后一个数的区间合并,新区间的最长前缀就是右边相邻的最长连续段(具体实现见代码,如果还是不太理解区间前缀和后缀的维护,可以先去看看这题)。

线段树每次单点修改和区间合并的复杂度都是 \(O(\log n)\),总复杂度为 \(O(n \log n)\),可以通过此题。

Code

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=100010;
struct Node
{
	int pre,suf;//当前区间的最长前缀和最长后缀 
	int pl,pr;//区间的左右端点 
}tree[N<<2];
Node pushup(Node &p,Node a,Node b)//pushup直接合并两个区间,便于找最长前缀和最长后缀 
{
	p.pl=a.pl,p.pr=b.pr;
	if(a.pre==a.pr-a.pl+1) p.pre=a.pre+b.pre;//如果左区间的前缀占满了整个左区间,则合并时也要把右区间算进去 
	else p.pre=a.pre;
	if(b.suf==b.pr-b.pl+1) p.suf=b.suf+a.suf;//如果右区间的前缀占满了整个左区间,则合并时也要把左区间算进去
	else p.suf=b.suf;
	return p; 
}
void build(int p,int pl,int pr)//因为最开始维护的区间是空的,所以也可以不用建树,这里只是方便记录每个区间的左右端点 
{
	tree[p].pl=pl,tree[p].pr=pr;
	if(pl==pr) return;
	int mid=(pl+pr)>>1;
	build(p<<1,pl,mid);
	build(p<<1|1,mid+1,pr); 
}
void update(int p,int id,int data)//标准的单点修改 
{
	if(tree[p].pl==tree[p].pr)
	{
		tree[p].pre=tree[p].suf=data;
		return;
	}
	int mid=(tree[p].pl+tree[p].pr)>>1;
	if(mid>=id) update(p<<1,id,data);
	if(mid<id) update(p<<1|1,id,data);
	pushup(tree[p],tree[p<<1],tree[p<<1|1]); 
}
Node mergeL(int p,int id)//合并最左端到编号为id的位置的区间 
{
	if(tree[p].pr<=id) return tree[p];
	int mid=(tree[p].pl+tree[p].pr)>>1;
	Node t;
	//如果当前区间中点在id右侧,就不用管右子区间,递归找左子区间 
	if(mid>=id) return mergeL(p<<1,id);//注意理解哪里该取等,这里容易写错 
	//如果当前区间中点在id左侧,那整个左区间一定在需合并的左区间中,右区间则继续递归找在1~id范围内的区间 
	if(mid<id) return pushup(t,tree[p<<1],mergeL(p<<1|1,id));
	
}
Node mergeR(int p,int id)//合并编号为id的位置到最右端的区间 
{
	if(tree[p].pl>=id) return tree[p];
	int mid=(tree[p].pl+tree[p].pr)>>1;
	Node t;
	if(mid<id) return mergeR(p<<1|1,id);
	if(mid>=id) return pushup(t,mergeR(p<<1,id),tree[p<<1|1]);
}
int n,m,ans=0x3f3f3f3f;
pair<int,int> lamp[N];//pair中first存数值大小(灯的高度),second存其在原数列中的编号,便于直接sort排序 
int main()
{
	scanf("%d%d",&n,&m);
	build(1,1,n);
	for(int i=1;i<=n;i++) scanf("%d",&lamp[i].first),lamp[i].second=i;
	sort(lamp+1,lamp+1+n);//对原数列从小到大排序 
	int l,r=0,cnt=0;//通过左右指针l和r枚举区间,cnt表示当前情况下的子列长度 
	for(l=1;l<=n;l++)
	{
		int Lsuf,Rpre;//左边最长后缀和右边最长前缀 
		while(cnt<m&&r<n)//一直右移右指针直到子列长度达到m 
		{
			r++;
			//维护的区间中新增一个可以选的数(当前右指针处) 
			update(1,lamp[r].second,1);
			if(lamp[r].second==1) Lsuf=0;
			else Lsuf=mergeL(1,lamp[r].second-1).suf;
			if(lamp[r].second==n) Rpre=0;
			else Rpre=mergeR(1,lamp[r].second+1).pre;
			//会增加子列长度的三种情况 
			if((!Lsuf&&!Rpre)||(!Rpre&&Lsuf&&Lsuf%2==0)||(!Lsuf&&Rpre&&Rpre%2==0)||(Lsuf&&Rpre&&Lsuf%2==0&&Rpre%2==0)) cnt++;
		}
		if(cnt==m)
		{
			//左指针右移,维护的区间中删除一个可以选的数(当前左指针处) 
			update(1,lamp[l].second,0);
			if(lamp[l].second==1) Lsuf=0;
			else Lsuf=mergeL(1,lamp[l].second-1).suf;
			if(lamp[l].second==n) Rpre=0;
			else Rpre=mergeR(1,lamp[l].second+1).pre;
			//会减少子列长度的三种情况 
			if((!Lsuf&&!Rpre)||(!Rpre&&Lsuf&&Lsuf%2==0)||(!Lsuf&&Rpre&&Rpre%2==0)||(Lsuf&&Rpre&&Lsuf%2==0&&Rpre%2==0)) cnt--;
			ans=min(lamp[r].first-lamp[l].first,ans);//每枚举到一种符合要求情况就更新一次最小答案 
		}
	}
	printf("%d",ans);
	return 0;
}

感谢各位dalao的阅读 qwq。文章来源地址https://www.toymoban.com/news/detail-604883.html

到了这里,关于luogu P9474 [yLOI2022] 长安幻世绘 详细题解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 2022 CSP-J 复赛题解

    【题目描述】 小文同学刚刚接触了信息学竞赛,有一天她遇到了这样一个题:给定正整数 a 和 b ,求 a b 的值是多少。 a b 即 b 个 a 相乘的值,例如 23 即为 3 个 2 相乘,结果为 2 × 2 × 2 = 8。 “简单!”小文心想,同时很快就写出了一份程序,可是测试时却出现了错误。 小文

    2024年02月07日
    浏览(48)
  • 2022CSP-J2题解

    今天(2022,10,29), C S P − J S CSP-JS C S P − J S 第二轮成功举办, 虽然大部分省市疫情取消 本蒟蒻今天有幸参加CSP,特发入门组题解 小文同学刚刚接触了信息学竞赛,有一天她遇到了这样一个题:给定正整数 a a a 和 b b b ,求 a b a^b a b 的值是多少。 a b a^b a b 即 b b b 个 a a a 相乘

    2023年04月08日
    浏览(68)
  • 【超好懂的比赛题解】2022CCPC四川省赛 个人题解

    title : “海康威视杯“ 2022年第十四届四川省大学生程序设计大赛 tags : ACM,练习记录 date : 2022-10-18 author : Linno 题目链接:https://ac.nowcoder.com/acm/contest/42105 出题数量:5/11 (顺序:K-F-B-H-A) A-Adjacent Swapping 显然可以先贪心移动(直接扫一遍)字符使前后两半在字符数量上是一样的

    2024年02月08日
    浏览(48)
  • 2022CSP-J 题解[完整版]

    “西西弗”的脑子是被宇宙射线影响了吗,造的题目我都写到睡着了…… 题目描述 小文同学刚刚接触了信息学竞赛,有一天她遇到了这样一个题:给定正整数 a a a 和 b b b ,求 a b a^b a b 的值是多少。 a b a^b a b 即 b b b 个 a a a 相乘的值,例如 2 3 2^3 2 3 即为 3 3 3 个 2 2 2 相乘,

    2024年02月10日
    浏览(58)
  • NewStarCTF 2022 web方向题解 wp

    先看题目,要传参加绕过。 分析一下代码:首先get一个data= data://test/plain, Wel…。然后key1和2用数组可以绕过。num=2077a可以绕过弱类型。eval()中的php语句被 # 注释了,我们需要通过换行 %0a 来忽视这个注释,所以再构造cmd=%0asystem(‘ls /’); 看看题干,伪随机数工具。 分析一下代

    2024年02月11日
    浏览(29)
  • 2022icpc西安站部分题解-E

    E. Find Maximum 题意:给定边界L和R,算满足的所有的的最大值, 其中满足: 。 题解: 打表发现发现了f(x)与x的三进制有关系,即f(x)等于x三进制的每个数相加,再加上三进制数的有效位数。下图从左向右依次是x,x的三进制,f(x)。 于是便是将问题转变为在区间中找到三进制的每

    2024年02月08日
    浏览(28)
  • 2022数学建模“五一杯”B题 题解+论文

    摘要 本文主要研究温度等因素对矿石加工质量控制问题。提高矿石加工质量,对节约不可再生资源和能源,推动节能减排,助力“双碳”’目标的实现,具有重要的意义。 针对问题一,我们要实现在给定系统温度和原矿参数的情况下,预测可能性最大的产品的指标。由于在

    2024年02月02日
    浏览(53)
  • 洛谷2022SCP第一轮J组模拟题(LGR-2022-J1)部分题解

    LGR-2022-J1 T3. 小恺编写了如下函数,希望计算斐波那契数列 f(n) 第 n 项对 10000 取余数的值:

    2024年02月09日
    浏览(24)
  • 2022蓝桥杯C++B组国赛真题题解

    目录 A:2022 B:钟表 C:卡牌 D:最大数字 E:出差 F:费用报销 G:故障 H:机房 I:齿轮 J:搬砖 问题描述 将 2022 拆分成 10 个互不相同的正整数之和, 总共有多少种拆分方法? 注意交换顺序视为同一种方法, 例如 2022=1000+1022 和 1022+1000 就视为同一种方法。 答案提交 这是一道结果填空的

    2024年02月06日
    浏览(27)
  • 【简单算法】2022SCUACM集训队冬季选拔全题解

    没想到现在冬季选拔都这么难了…… 题目传送门 本文juejin:https://juejin.cn/post/7222531019319722039/ 本文CSDN:https://blog.csdn.net/hans774882968/article/details/130184851 作者:hans774882968以及hans774882968以及hans774882968 参考:https://www.cnblogs.com/ycx-akioi/p/AtCoder-abc165.html 最长严格上升子序列树上版本

    2023年04月16日
    浏览(27)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包