专栏文章

题解:P13518 [KOI 2025 #2] 镜子

P13518题解参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@miol06oz
此快照首次捕获于
2025/12/02 20:56
3 个月前
此快照最后确认于
2025/12/02 20:56
3 个月前
查看原文

1.样例解释

由于样例 2 和 3 没有给出解释,作者做这题时,对着样例思考了很久,故作者太菜了在这里给出样例解释。样例 1 和 4 答案显然,不作解释。

1-1.样例 #2

样例输入:
CPP
6 3
-4 -2 2 6 8 9
样例输出:
CPP
57
样例解释:最优的方法如下(下表中 a,ba,b 的意义与原题一致,AA 数组即为原题中输入的镜子位置,初始令 a=sa = sss 的意义见原题)。
顺序使用镜子编号镜子的位置(bb此时答案(2ba2b - a)
1\bm111A[1]=4A[1] = -42×(4)3=112 \times (-4) - 3 = -11
2\bm266A[6]=9A[6] = 92×9(11)=292 \times 9 - (-11) = 29
3\bm322A[2]=2A[2] = -22×(2)29=332 \times (-2) - 29 = -33
4\bm455A[5]=8A[5] = 82×8(33)=492 \times 8 - (-33) = 49
5\bm533A[3]=2A[3] = 22×249=452 \times 2 - 49 = -45
6\bm644A[4]=6A[4] = 62×6(45)=572 \times 6 - (-45) = 57
最终答案为 5757

1-2.样例 #3

样例输入:
CPP
9 9
0 1 3 3 4 5 8 9 10
样例输出:
CPP
49
样例解释:最优的方法如下(下表中 a,ba,b 的意义与原题一致,AA 数组即为原题中输入的镜子位置,初始令 a=sa = sss 的意义见原题)。
顺序使用镜子编号镜子的位置(bb此时答案(2ba2b - a)
1\bm199A[9]=10A[9] = 102×109=112 \times 10 - 9 = 11
2\bm211A[1]=0A[1] = 02×011=112 \times 0 - 11 = -11
3\bm388A[8]=9A[8] = 92×9(11)=292 \times 9 - (-11) = 29
4\bm422A[2]=1A[2] = 12×129=272 \times 1 - 29 = -27
5\bm577A[7]=8A[7] = 82×8(27)=432 \times 8 - (-27) = 43
6\bm633A[3]=3A[3] = 32×343=372 \times 3 - 43 = -37
7\bm766A[6]=5A[6] = 52×5(37)=472 \times 5 - (-37) = 47
8\bm844A[4]=3A[4] = 32×347=412 \times 3 - 47 = -41
9\bm955A[5]=4A[5] = 42×4(41)=492 \times 4 - (-41) = 49
最终答案为 4949

2.题目思路

由于题目要求求出最大值,故考虑贪心。
当我们看懂样例时,我们就大致明白了贪心的方法(为了方便解释,下文中将“角色的位置移动到以镜子为中心的对称点”简称为“跳跃”):从初始位置开始,找到最后一个镜子向右跳跃一步,找到最前一个镜子向左跳跃一步,两者交替进行。
但是,观察两个样例发现,两组样例的最优解第一步选择了相反的方向“跳跃”!
观察子任务的特殊性质,我们发现:子任务中出现了“NN 为偶数”限制,这启发我们将本题分类讨论,以 NN 的奇偶性来讨论。
NN 为偶数时,最优方法为先向左,再向右,如此交替进行,这样答案就会先变小后变大,当 N2\dfrac{N}{2} 次交替“跳跃”结束后,答案必然为最大值。
NN 为奇数时,最优方法为先向右“跳跃”一次,再进行 N12\dfrac{N - 1}{2} 次交替“跳跃”,如果不先向右,那么最终答案必然在镜子左侧,错误。
我们可以用一个布尔变量记录当前是否要向右“跳跃”(记录是否向左也可),再用两个指针分别记录左右两侧当前使用的镜子编号。如果布尔变量值为真,则记录右侧编号的指针左移一个,否则记录左侧编号的指针右移一个。由于每个镜子只能用一次,故答案循环 NN 次即可。
时间复杂度 O(N)\mathcal O(N)

3.贪心算法的正确性

我们感性理解一下。
要求答案最大,即求 a,ba,b,使“跳跃”完成后 2ba2b - a 最大。
要使上述式子最大,需要使 bb 最大,aa 最小。要想使 aa 最小,就需要使上一个 2ba2b - a 的结果(这个结果会是新的 aa)最小,于是就尝试往左边最前一个镜子“跳跃”,要想使上一个 2ba2b - a 最小,就要使 bb 最小,aa 最大。要想使 aa 最大,就需要使上上一个 2ba2b - a 的结果最大,于是尝试往右边最后一个镜子“跳跃”……以此类推 NN 次,可以发现如果每步都是最优,最终得到的一定是最优解。

4.代码及注意问题

程序时间优化技巧
  1. 当输入量较大时,使用快读(见代码);
  2. 使用 <<1\texttt{<<1} 代替 *2\texttt{*2}
  3. 使用 if(n&1)\texttt{if(n\&1)} 代替 if(n%2==1)\texttt{if(n\%2==1)}
代码CPP
#include<iostream>
#include<cstdio>
using namespace std;
const int max_n=200005;
int n,s,a[max_n],l,r;
bool toright;
long long ans; //开 long long!
inline int read(){ //快读
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
    return x*f;
}
int main(){
    n=read(),s=read();
    for(int i=1;i<=n;i++){
        a[i]=read();
    }
    if(n&1){ //特判奇数,此时要先向右跳。n&1 可以快速判断 n 是否为奇数,是竞赛常用方法。
        toright=true;
    }
    l=1,r=n;
    ans=s;
    for(int i=1;i<=n;i++){
        if(toright){
            ans=(a[r]<<1)-ans; //<<1 相当于 *2,但更快
            r--; //指针向左移 1 个
        }
        else{
            ans=(a[l]<<1)-ans; //<<1 相当于 *2,但更快
            l++; //指针向右移 1 个
        }
        toright=!toright; //对原先的布尔值取反(即 true 变为 false,或 false 变为 true)。
    }
    printf("%lld\n",ans);
    return 0;
}

5.后记

更多内容,请移步至:
  1. Luogu ryf2011\color{red}\texttt{Luogu ryf2011}
  2. cnblogs(博客园) cnblogs2011ryf\color{orange}\texttt{cnblogs(博客园) cnblogs2011ryf}

评论

0 条评论,欢迎与作者交流。

正在加载评论...