蓝桥杯[第十届][B组]-j.灵能传输
2022-03-26 02:33 幻霞 阅读(82) 评论(1) 编辑 收藏 举报
题目来自蓝桥杯练习系统:http://lx.lanqiao.cn/problem.page?gpid=T2704
一.简单介绍
由星际争霸作引子,引出一道有些费解的题目,一开始笔者没有如何思路,试探性的写了一些优化判断与操作:因为操作不会使中间的值产生绝对值变化,如果操作能使得左右值取得更小的值,则优化成立,执行操作.
但是只通过了两个样例,显然是不行的。笔者去查阅了一下相关资料和大佬的代码,虽然十分难理解,但也总结了一些获得新的知识和本题的解题方法:
二.相关知识
前缀和:简言之就是建立一个sum数组,索引i代表从0~i目标数组的和,迭代也很简单,输入后 sum[i]=sum[i-1]+num[i];即可(注意越界),这种处理方式在很多题解中见过,每一个输入数组num[i]的值可以用sum[i]-sum[i-1]的方式得到。
贪心:一种算法的解题思路,每次取能取到的最优解,即取局部的最优解 ,而很多问题的最优解便是从每次的局部最优解得到的,它与动态规划有一些相同和不同的地方,动态规划常见式dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i])(以01背包的动态规划公式为例),动态规划也有比较获得最优方案的地方,但动态规划利用空间存储了之前的最优解,允许在某一部做出牺牲以换取整体的最优解。
三.操作转化
本题可以选择前缀和来处理数组,可以发现如果输入的是x,y,z(y>0),和数组sum为:x,x+y,x+y+z. 进行操作后原数组为x+y,-y,z+y,和数组sum变为:x+y,x,x+y+z,发现前缀和的前两位做了交换,后面的不变。那它的意义是什么呢?其实它的意义是交换而不变化,无论怎么操作,sum数组不发生变化,而根据前缀和sum数组,我们可以通过sum[i]-sum[i-1]得到每一位的值,而这个值是会根据操作变化的。事实上我们把原数组的操作产生的值变化转化为前缀和数组某两个位置之间的值交换,这样其实如果我们把和数组排序得到一个尽可能单调的数组,也就是排个序,其实可以去理解为去对原数组的一些位置进行依次操作。得到的递增或递减的前缀和后,其中的相邻数值的差会变小,两者在高度上会尽可能接近(相较于原来的波动数组来说),无论正差还是逆差(大小关系),总之,整个数组相邻的值之间最大差的值降低为最小值,这样,就能得到最终题目要求的不稳定值最小。
四.题目特点与解决方案
不过这一题只能操作去掉两端的位置,这导致了最后一个值无法与前面的值交换(操作效果是交换3个中的前两个,但条件是三个值必须在索引内)如果刚好第一个是最小值,最后一个是最大值,那么我们不用去考虑别的直接排序就行,但如果不是这样的话,我们可以去这样操作:定义前缀和sum[0]=0,把它和sum最后一个值比较,如果s0>sn,则交换两个值,确保s0<sn,排序后,找到s0和sn的索引值,然后s0向前遍历,步长为2,同理sn向后,其余的按排序顺序安排。
首先暂停一下,我们知道,因为最后一个值不会去移动,所以很可能排完序后最后的一个邻值差比答案的大,所以要尽可能保证在单调的情况下分配数值使得最后两个数的差不会过大,至于步长为何为2,是因为需要空出一位在回滚的时候不至于让落差太大。而且在定义了0的情况下,前缀和可能有负数在0的前面,而0最后一定是放在最后或者是第一个,不管它在哪,与相邻数相减的结果是相邻邻数的值。因此不用去担心它对求解的影响
五.示例
举例 n=10; -9,5,7,-10,3,-8,6,9,7,-11,可以得到前缀和sum:0,-9,-4,3,-7,-4,-12,-6,3,10,-1 ,排序得:-12,-9,-7,-6,-4,-4,-1,0,3,3,10,排序前最后一个为-1,第一个为0,顺序应为-1,0,-1向左遍历,步长为2,得到序列[-1 -4 -7 -12 -9] [3 0],把剩下的未被遍历到的递增插入数组得到 [-1 -4 -7 -12] [-9 -6 -4 3 10] [3 0],会发现这是个↘↗↘的折线图,事实上-1应该在最后,但是为了方便排序处理如果它比零小就放在前面,可以把它对称回去使-1回到最后的位置,结果不会发生变化。步长为2是为了交叉取值,如果为1的话解序列是-1 -4 -4 -6 -7 -9 -12 10 3 3 0,可以发现产生10--12=22的新的最大值,形成了一个大的落差,这是不应该的。
六.总结
总体上,因为最后一个不变,所以从后往前应该是逐渐变大或变小以适应情况,整个前缀和必然存在smin,smax,s0,sn。从s0开始到sn经过min和max值,根据对称性令s0<sn以向前滚,sn向后滚,步长为2交叉相叠加以避免大落差。其实可以发现整个过程都是因为最后一个值不能移动所以构建一个平缓的序列连接最后一个值,最小值和最大值的。
https://www.bilibili.com/video/av47356111,yxc大佬的这个图比较清晰地描述解序列的状态。
最后附上代码,代码链接:https://blog.51cto.com/u_15127651/3695779
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 300010; typedef long long LL; int n; bool st[N]; LL ans,sum[N],a[N]; int main() { int T; scanf("%d",&T); while(T--) { scanf("%d",&n); ans = 0; sum[0] = 0; memset(st,0,sizeof st); for(int i=1;i<=n;i++) scanf("%lld",&sum[i]),sum[i]+=sum[i-1]; LL f = sum[0],e = sum[n]; int l = 0,r = n; if(f>e) swap(f,e);//如果不互换,那么s[0]向左找的时候可能与s[n]重叠. sort(sum,sum+n+1); for(int i=0;i<=n;i++) if(sum[i]==f) { f = i; break; } for(int i=n;i>=0;i--) if(sum[i]==e) { e = i; break; } for(int i=f;i>=0;i-=2)//s[0]放在第一个 a[l++] = sum[i],st[i] = 1; for(int i=e;i<=n;i+=2)//s[n]放在最后一个 a[r--] = sum[i],st[i] = 1; for(int i=0;i<=n;i++) if(!st[i]) a[l++] = sum[i]; for(int i=1;i<=n;i++) ans = max(ans,abs(a[i]-a[i-1])); printf("%lld\n",ans); } return 0; }
这一题思路上是比较困难,笔者写到这里也只是大概明白了一些缘由,有遗漏处欢迎补充。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架