第十届蓝桥杯B组省赛灵能传输题解
题目分析
问题转化
令
S
k
=
∑
i
=
1
k
a
i
S_k=\sum_{i=1}^k{a_i}
Sk=i=1∑kai
选中某个"高阶圣堂武士"
a
i
a_i
ai的结果是
S
i
−
1
+
a
i
,
S
i
−
a
i
S_{i-1}+a_i,S_i-a_i
Si−1+ai,Si−ai即
s
w
a
p
(
S
i
,
S
i
−
1
)
swap\left(S_i,S_{i-1}\right)
swap(Si,Si−1)
设原前缀序列为
S
0
,
S
1
,
S
2
,
.
.
.
,
S
n
S_0,S_1,S_2,...,S_n
S0,S1,S2,...,Sn
题目要转化为:
求序列
{
t
i
}
=
t
1
,
t
2
.
.
.
,
t
n
+
1
,
其
中
(
t
1
=
0
,
t
n
+
1
=
n
)
\left\{t_i\right\}=t_1,t_2...,t_{n+1},其中(t_1=0,t_{n+1}=n)
{ti}=t1,t2...,tn+1,其中(t1=0,tn+1=n)
使
f
=
max
(
∣
S
t
(
i
)
−
S
t
(
i
−
1
)
∣
)
,
i
∈
[
1
,
n
+
1
]
f=\max\left(\left|S_{t_{(i)}}-S_{t_{(i-1)}}\right|\right),i\in\left[1,n+1\right]
f=max(∣∣St(i)−St(i−1)∣∣),i∈[1,n+1]
取得最小值
算法设计
首先想到的是对
s
i
s_i
si排序,因为排序后与
s
i
s_i
si相邻的元素一定与
s
i
s_i
si差的最少。但是最开始的0与最后的
s
n
s_n
sn是不可能移动的,这是算法设计的主要障碍。
解决方案如图所示
从0与
S
n
S_n
Sn中选取较小的作为
S
L
S_L
SL,(也就是图中的B),然后以间隔2跳跃到最小值(图中的L).选大的中间至少会有间隔为3的情况(数数中间那条线与曲线的交点),很不合理,而选大的往最大值跳与选小的往最小值跳等价
最后一跳间隔可能是1,这取决于0在序列中的位置(考虑最小的是K而非L)
原理很简单,如果选择了路径BC那么也就选择了AD,而AD显然大于BD和CA,依次类推,选择间隔2跳跃是合理的。中间的连续访问即可,最右边也要跳跃访问,策略与左侧类似。
然后按照该方法在连续数列(1,2,3,…,n)中访问即可得到
{
t
i
}
\left\{t_i\right\}
{ti}
例如给定L,R,N对应的序列为,
t
0
t_0
t0未打印。
跳跃测试C++代码
#include<bits\stdc++.h>
#define LEFT 0
#define RIGHT 1
using namespace std;
int num,L,R,D,per;
int engin(int i){//跳跃遍历引擎
if(num<3)return num;
if(D==LEFT){
if(i<=L){//从L向1靠
if(i!=1&&i!=2){//一切正常
return i-2;
}
else{//到头了
D=RIGHT;//改变方向
if(i==2)return 1;
if(i==1&&R!=2)return 2;
return 3;
}
}
if(i>R){//从最右向R靠
if(i==R+1)return R;
return i-2;
}
}else{
if(i<L){//从左向lL
if(i==1){
if(per==3)return 2;
}
if(i+2!=R)return i+2;//不能提前跳到R上
if(i+2==R&&R<num)return i+3;//如果有空间可以考虑R的下一个
if(i+2==num&&i+2==R)return num;//如果就剩一个了那就结束
return i+2;
}
if(i>L&&i<R){//从L到R
if(i<R-1)return i+1;
if(i==R-1&&R<num)return i+2;//不提前访问
if(i==R-1&&R==num)return num;//除非是最后一个
}
if(i>R){//从R向最右
if(i!=num&&i!=num-1){//正常状态
return i+2;
}else{//到头了
D=LEFT;//改变方向
if(i==num-1)return num;
if(i==num)return num-1;
}
}
}
}
int next(int i){
int ret=engin(i);
per=i;
return ret;
}
int main(){
while(1){
cin >> L >> R >> num;
int i=L;
D=LEFT;
while(i!=R){
i=next(i);
cout << per << " ";
}
cout << i << endl;
}
return 0;
}
完整C++代码
(由于输入规模太大,最后两组数据会擦边
有时100分有时92分,
加上ios::sync_with_stdio(false)后直接提速700ms!!!)
#include<bits\stdc++.h>
#define N 300010
#define LEFT 0
#define RIGHT 1
using namespace std;
int T,num,L,R,D,per;
long long w[N],*warrior=w+2;//方便整体搬移
int cmp(const void* a,const void* b){
long long temp=*(long long*)a-*(long long*)b;
if(temp>0)return 1;
if(temp<0)return -1;
return 0;
}
int engin(int i){//跳跃遍历引擎
if(num<3)return num;
if(D==LEFT){
if(i<=L){//从L向1靠
if(i!=1&&i!=2){//一切正常
return i-2;
}
else{//到头了
D=RIGHT;//改变方向
if(i==2)return 1;
if(i==1&&R!=2)return 2;
return 3;
}
}
if(i>R){//从最右向R靠
if(i==R+1)return R;
return i-2;
}
}else{
if(i<L){//从左向lL
if(i==1){
if(per==3)return 2;
}
if(i+2!=R)return i+2;//不能提前跳到R上
if(i+2==R&&R<num)return i+3;//如果有空间可以考虑R的下一个
if(i+2==num&&i+2==R)return num;//如果就剩一个了那就结束
return i+2;
}
if(i>L&&i<R){//从L到R
if(i<R-1)return i+1;
if(i==R-1&&R<num)return i+2;//不提前访问
if(i==R-1&&R==num)return num;//除非是最后一个
}
if(i>R){//从R向最右
if(i!=num&&i!=num-1){//正常状态
return i+2;
}else{//到头了
D=LEFT;//改变方向
if(i==num-1)return num;
if(i==num)return num-1;
}
}
}
}
int next(int i){//获取目标序列中下一个元素的下表并保存当前值
int ret=engin(i);
per=i;
return ret;
}
int main(){
ios::sync_with_stdio(false);//不加刚好卡在一秒,加了提速700ms
cin >> T;
while(T--){
cin >> num;
memset(w,0,sizeof(w));
for(int i=1;i<=num;++i){//前缀和,包括0的
cin >> warrior[i];
warrior[i]+=warrior[i-1];
}
long long temp=warrior[num];
qsort(warrior,num+1,sizeof(long long),cmp);
++num;
//所有元素右移O(1)时间复杂度
//让元素下标从1开始是"跳跃遍历"引擎的要求!
warrior=warrior-1;
//拣择起点与终点
for(int i=0;i<=num;++i){
if(warrior[i]==0)L=i;
if(warrior[i]==temp)R=i;
}
//先择小的为起点
if(temp<0)swap(L,R);
int i=L;//从L开始
long long dmax=0;
D=LEFT; //先往最小值跳
while(i!=R){ //访问到Sn就结束了
i=next(i);
dmax=max(dmax,abs(warrior[i]-warrior[per]));
}
dmax=max(dmax,abs(warrior[i]-warrior[per]));
cout << dmax << endl;
warrior=warrior+1;//记得搬回去,不然一会就不知道搬哪去了,
}
return 0;
}