蓝桥云课-5. 花灯调整【算法赛】

目录

题目:

解题思路:

代码:

问题:

总结:


5.花灯调整【算法赛】 - 蓝桥云课

题目:

解题思路:

从前往后统计其后缀和,最后判断与当前位置翻转是否相同即可(偶数相当于没有翻转,奇数翻转)

代码:

java 复制代码
 /*out
*Stack Integer ArrayList String StringBuffer peek
*Collections imports LinkedList offer return
*empty polls offerLast pollFirst isEmpty
*List Deque append length HashMap
*return remove boolean continue charAt
*toString static System println nextInt
*Scanner  System  toCharArray contains
*/
import java.util.*;
public class Main {
    static Scanner sc = new Scanner(System.in);
    public static void main(String[] args) {
        int t=1;
        while(t--!=0){
          slove();
        }
        sc.close();
    }
    public static void slove(){
      int n=sc.nextInt(),k=sc.nextInt();
      String s=sc.next();
      int [] p=new int[n];
      for(int i=0;i<k;i++){
        p[sc.nextInt()-1]++;
      }
      int[] ciShu=new int[n];
      ciShu[n-1]=p[n-1];
      for(int i=n-2;i>=0;i--){
        ciShu[i]=p[i]+ciShu[i+1];
      }
      char[] tar=s.toCharArray();
      for(int i=0;i<tar.length;i++){
        int a=tar[i]-'0';
        if(a%2!=ciShu[i]%2){
          System.out.println("No");
          return ;
        }
      }
      System.out.println("Yes");
    }
}

问题:

总结:

【算法解析】后缀和解决 "花灯调整" 策略判定问题:从问题本质到代码实现

在算法竞赛中,"操作叠加的状态判定"类问题是高频考点之一 ------ 这类问题的核心是 "操作的顺序不影响最终状态,仅与操作次数的奇偶性有关"。本文以 "花灯调整" 问题为例,从问题拆解、核心规律、后缀和原理、代码实现等维度,全面讲解如何用后缀和高效解决这类问题,同时深入剖析代码的每一个细节,帮助你掌握这类问题的通用解法。

一、问题背景与需求拆解

在开始代码分析前,我们先彻底理解问题的规则与目标,这是后续算法设计的基础。

1.1 问题规则

初始状态:n盏花灯,全部处于关闭状态(用 0 表示);

游客操作:每位游客选择一个 "前缀p"(即从第 1 盏到第p盏灯的连续区间),将该区间内所有花灯的状态反转(0 变 1,1 变 0);

目标状态:给定一个长度为n的 01 字符串S,其中S[i]表示第i+1盏灯的目标状态(S[i]='1'表示亮,S[i]='0'表示灭);

操作顺序:游客可以任意调整操作的执行顺序;

问题目标:判断是否存在一种操作顺序,使得最终所有花灯的状态与目标串S完全一致。

1.2 需求转化为数学模型

我们的核心任务是:

验证 "游客操作对每盏灯的反转次数的奇偶性" 是否恰好等于 "从初始状态(0)到目标状态(S[i])所需的反转次数的奇偶性"。

原因很简单:

反转操作是幂等的(反转偶数次等价于未反转,反转奇数次等价于反转 1 次);

操作顺序不影响最终状态(因为反转是 "叠加" 的,先反转前 3 盏再反转前 5 盏,与先反转前 5 盏再反转前 3 盏,每盏灯的反转次数完全相同)。

二、核心规律:反转次数的统计与后缀和的应用

要解决问题,首先需要明确每盏灯的反转次数如何计算------ 这是问题的关键突破口。

2.1 每盏灯的反转次数的定义

对于第i盏灯(索引从 0 开始),它的反转次数 = 选择了 "前缀p ≥ i+1" 的游客数量之和(因为游客选前缀p时,会反转前p盏灯,第i盏灯属于前p盏的条件是p ≥ i+1)。

举个例子:

第 0 盏灯(对应实际第 1 盏)的反转次数 = 选了p≥1的游客数量之和;

第 2 盏灯(对应实际第 3 盏)的反转次数 = 选了p≥3的游客数量之和。

2.2 为什么用 "后缀和" 统计反转次数

统计 "选了p ≥ x的游客数量之和",本质是对 "游客选择的前缀数组" 做 "从 x 到末尾的累加"------ 这正是后缀和的典型应用场景:

前缀和:从左到右累加,统计 "前 x 项的和";

后缀和:从右到左累加,统计 "从 x 到末尾的和"。

因此,我们可以用后缀和快速计算每盏灯的反转次数,时间复杂度为O(n),完全满足n ≤ 10^5的规模要求。

三、代码整体结构解析

我们先从宏观上看代码的模块划分,理解代码是如何对应问题需求的:

java

运行

import java.util.*;

public class Main {

static Scanner sc = new Scanner(System.in);

public static void main(String[] args) {

int t=1;

while(t--!=0){ // 兼容多组测试用例(此处t=1,仅执行1次)

slove();

}

sc.close();

}

public static void slove(){

// 1. 输入读取与初始化

int n=sc.nextInt(),k=sc.nextInt();

String s=sc.next();

int [] p=new int[n];

for(int i=0;i<k;i++){

p[sc.nextInt()-1]++;

}

// 2. 计算每盏灯的反转次数(后缀和)

int[] ciShu=new int[n];

ciShu[n-1]=p[n-1];

for(int i=n-2;i>=0;i--){

ciShu[i]=p[i]+ciShu[i+1];

}

// 3. 对比反转次数的奇偶性与目标状态

char[] tar=s.toCharArray();

for(int i=0;i<tar.length;i++){

int a=tar[i]-'0';

if(a%2!=ciShu[i]%2){

System.out.println("No");

return ;

}

}

// 4. 输出结果

System.out.println("Yes");

}

}

代码分为 4 个核心模块:

输入读取模块:将输入的 "前缀选择" 统计到数组中;

后缀和计算模块:用后缀和得到每盏灯的反转次数;

奇偶性对比模块:验证反转次数的奇偶性是否匹配目标状态;

输出模块:根据验证结果输出 "Yes" 或 "No"。

四、输入读取模块:结构化存储游客的选择

输入处理是算法题的基础,结构化存储能避免后续逻辑中 "变量混淆" 的问题,我们详细解析这部分代码的设计思路。

4.1 输入的对应关系

问题的输入分为三部分:

第一行:n(花灯数)、k(游客数);

第二行:目标串S(长度为n的 01 字符串);

第三行:k个整数p_1~p_k(每位游客选择的前缀)。

4.2 代码的输入处理逻辑

java

运行

// 读取n和k

int n=sc.nextInt(),k=sc.nextInt();

// 读取目标串s

String s=sc.next();

// 定义数组p:p[x]表示"选择了前缀x+1的游客数量"(x是0-based索引)

int [] p=new int[n];

for(int i=0;i<k;i++){

// 游客输入的p是1-based(比如选前缀3对应实际第3盏),转为0-based索引

int choose = sc.nextInt() - 1;

p[choose]++;

}

关键细节:

游客输入的 "前缀p" 是1-based(比如选 "前缀 3" 表示前 3 盏灯),而代码中数组是0-based(索引 0 对应实际第 1 盏),因此需要sc.nextInt() - 1将输入转为数组索引;

数组p的含义:p[x]表示 "选择了前缀x+1的游客数量"(比如p[2]表示选了前缀 3 的游客数)。

4.3 样例输入的存储结果

以样例输入为例:

plaintext

样例输入:

5 3

10010

4 3 1

n=5,k=3,目标串s="10010";

游客选择的前缀是 4、3、1,转为 0-based 索引是 3、2、0;

数组p的结果:p[0]=1(选前缀 1 的游客数)、p[2]=1(选前缀 3 的游客数)、p[3]=1(选前缀 4 的游客数),其余为 0 → p = [1,0,1,1,0]。

五、后缀和计算模块:核心逻辑的实现

这是代码的灵魂部分 ------ 用后缀和计算每盏灯的反转次数,我们从原理、代码、样例验证三个维度详细解析。

5.1 后缀和的原理回顾

后缀和的定义是:

对于数组arr,后缀和数组suffixSum满足:suffixSum[i] = arr[i] + suffixSum[i+1](从右到左计算)。

对于我们的问题,数组p存储了 "每个前缀被选择的次数",因此:

第i盏灯(0-based)的反转次数 = p[i] + p[i+1] + ... + p[n-1] → 这正是后缀和数组suffixSum[i]的值。

5.2 后缀和的代码实现

java

运行

// 定义ciShu数组:ciShu[i]表示第i盏灯的反转次数

int[] ciShu=new int[n];

// 初始化最后一盏灯的反转次数(只有选了前缀n的游客会影响它)

ciShu[n-1]=p[n-1];

// 从倒数第二盏灯往前计算后缀和

for(int i=n-2;i>=0;i--){

ciShu[i] = p[i] + ciShu[i+1];

}

代码逻辑拆解:

最后一盏灯(i=n-1)的反转次数 = 选了前缀n的游客数(即p[n-1]);

第i盏灯的反转次数 = 选了前缀i+1的游客数(p[i]) + 第i+1盏灯的反转次数(因为选前缀≥i+2的游客也会影响第i盏灯)。

5.3 样例的后缀和计算结果

以样例的p = [1,0,1,1,0]为例:

ciShu[4] = p[4] = 0(最后一盏灯,选前缀 5 的游客数为 0);

ciShu[3] = p[3] + ciShu[4] = 1 + 0 = 1(第 4 盏灯,选前缀 4 的游客数 + 第 5 盏的反转次数);

ciShu[2] = p[2] + ciShu[3] = 1 + 1 = 2(第 3 盏灯,选前缀 3 的游客数 + 第 4 盏的反转次数);

ciShu[1] = p[1] + ciShu[2] = 0 + 2 = 2(第 2 盏灯,选前缀 2 的游客数 + 第 3 盏的反转次数);

ciShu[0] = p[0] + ciShu[1] = 1 + 2 = 3(第 1 盏灯,选前缀 1 的游客数 + 第 2 盏的反转次数);

最终ciShu数组(每盏灯的反转次数)为:[3,2,2,1,0]。

六、奇偶性对比模块:判定是否可行的核心

这部分代码的作用是验证 "每盏灯的反转次数的奇偶性" 是否与 "目标状态所需的反转次数的奇偶性" 一致 ------ 这是问题的最终判定条件。

6.1 目标状态与反转次数的奇偶性

初始状态下,所有灯都是 0,要达到目标状态S[i](0 或 1):

若S[i] = '0':不需要反转,或反转偶数次(最终状态为 0);

若S[i] = '1':需要反转奇数次(最终状态为 1)。

因此,目标状态所需的反转次数的奇偶性 = S[i] - '0'(将字符转为整数 0 或 1)。

6.2 代码的奇偶性对比逻辑

java

运行

// 将目标串转为字符数组,方便逐位访问

char[] tar = s.toCharArray();

// 遍历每盏灯

for(int i=0;i<tar.length;i++){

// 将字符'0'/'1'转为整数0/1,得到目标所需的奇偶性

int targetParity = tar[i] - '0';

// 计算当前灯反转次数的奇偶性

int actualParity = ciShu[i] % 2;

// 若奇偶性不匹配,直接输出"No"并返回

if(targetParity != actualParity){

System.out.println("No");

return ;

}

}

6.3 样例的奇偶性对比验证

样例中:

目标串s="10010" → tar = ['1','0','0','1','0'];

目标所需的奇偶性:[1,0,0,1,0];

实际反转次数的奇偶性(ciShu % 2):[3%2=1, 2%2=0, 2%2=0, 1%2=1, 0%2=0];

两者完全一致,因此输出 "Yes",与样例结果匹配。

七、边界情况与性能分析

在算法题中,边界处理和性能是衡量代码质量的关键指标,我们分析代码在这两方面的表现。

7.1 边界情况的处理

代码能正确处理以下边界情况:

所有灯都是 0:目标所需的奇偶性全为 0,只需验证实际反转次数全为偶数;

所有灯都是 1:目标所需的奇偶性全为 1,只需验证实际反转次数全为奇数;

游客数量为 0:此时所有灯的反转次数为 0,只需目标串全为 0;

游客选择的前缀全为 n:此时所有灯的反转次数等于游客数量,只需验证所有灯的目标奇偶性相同。

7.2 性能分析

代码的时间复杂度为O(n + k):

输入处理:O(k)(遍历 k 个游客的选择);

后缀和计算:O(n)(遍历 n 盏灯);

奇偶性对比:O(n)(遍历 n 盏灯)。

空间复杂度为O(n):

数组p和ciShu的空间都是O(n),完全满足n ≤ 10^5的题目限制。

八、代码的优化与拓展

虽然当前代码已经能正确解决问题,但我们可以从可读性、扩展性角度做一些优化,让代码更通用。

8.1 变量名的语义化优化

原代码中ciShu(次数)的变量名不够直观,可优化为reverseCount(反转次数);p可优化为prefixChooseCount(前缀选择次数):

java

运行

// 优化后

int[] prefixChooseCount = new int[n];

for(int i=0;i<k;i++){

int choose = sc.nextInt() - 1;

prefixChooseCount[choose]++;

}

int[] reverseCount = new int[n];

reverseCount[n-1] = prefixChooseCount[n-1];

for(int i=n-2;i>=0;i--){

reverseCount[i] = prefixChooseCount[i] + reverseCount[i+1];

}

8.2 兼容多组测试用例

原代码中while(t--!=0)的t=1,仅支持单组测试用例。若要支持多组,只需将t改为sc.nextInt():

java

运行

public static void main(String[] args) {

int t = sc.nextInt(); // 读取测试用例数

while(t--!=0){

slove();

}

sc.close();

}

8.3 拓展:处理 "反转区间" 的通用问题

这类 "操作区间、统计每点的操作次数" 的问题,都可以用差分 + 前缀和的方式解决(后缀和是前缀和的一种变体)。例如:

若操作是 "反转区间[l, r]",可以用差分数组记录区间的变化,再用前缀和得到每点的操作次数。

九、总结

本文以 "花灯调整" 问题为例,详细讲解了后缀和在 "操作叠加的状态判定" 类问题中的应用:

问题拆解:将 "状态匹配" 转化为 "反转次数的奇偶性匹配";

核心规律:操作顺序不影响最终状态,仅与操作次数的奇偶性有关;

算法选择:用后缀和快速统计每盏灯的反转次数,时间复杂度O(n);

代码实现:输入处理→后缀和计算→奇偶性对比→输出结果,模块清晰。

这类问题的通用解题步骤是:

分析操作对每个元素的影响;

用前缀和 / 后缀和 / 差分统计每个元素的操作次数;

验证操作次数的奇偶性是否匹配目标状态。

掌握这一思路后,你可以轻松解决类似的 "开关灯""翻转字符串" 等问题 ------ 核心是抓住 "操作的幂等性" 和 "区间统计的前缀和 / 后缀和技巧"。

相关推荐
猷咪21 分钟前
C++基础
开发语言·c++
IT·小灰灰23 分钟前
30行PHP,利用硅基流动API,网页客服瞬间上线
开发语言·人工智能·aigc·php
快点好好学习吧24 分钟前
phpize 依赖 php-config 获取 PHP 信息的庖丁解牛
android·开发语言·php
秦老师Q25 分钟前
php入门教程(超详细,一篇就够了!!!)
开发语言·mysql·php·db
烟锁池塘柳025 分钟前
解决Google Scholar “We‘re sorry... but your computer or network may be sending automated queries.”的问题
开发语言
是誰萆微了承諾25 分钟前
php 对接deepseek
android·开发语言·php
vx_BS8133029 分钟前
【直接可用源码免费送】计算机毕业设计精选项目03574基于Python的网上商城管理系统设计与实现:Java/PHP/Python/C#小程序、单片机、成品+文档源码支持定制
java·python·课程设计
2601_9498683629 分钟前
Flutter for OpenHarmony 电子合同签署App实战 - 已签合同实现
java·开发语言·flutter
yyy(十一月限定版)43 分钟前
寒假集训4——二分排序
算法
星火开发设计43 分钟前
类型别名 typedef:让复杂类型更简洁
开发语言·c++·学习·算法·函数·知识