关于Scrum+XP+DevOps的学习
最近听了ECUG
大会上孙敬云老师的分享感觉受益匪浅,毕竟大学课本上只讲到瀑布模型
就没有下文了,工作以后一直贯彻的都是Scrum
路线,一直也没有时间好好的去学习整理这部分的知识,直到近几天听到了孙老师的分享,所以就在这里记录下孙老师的分享也总结我自己的思路。以下内容部分摘自于孙老师的分析PPT
1 软件工程之路
1.1 软件工程的演进
貌似大学的那门软件工程只给我们讲到了1980年,之后的需要我们走出校门,在社会中进行学习。
先来看下面这张图,是1980年至今的软件工程演化路线,像瀑布模型大家应该是耳熟能详。
进入1990年,Scrum这种,近几年应该也是略有耳闻,可是像极限编程
这种可能就很少听说了吧。
再向后看,进入2000年,持续集成
也就是CI/CD
中的CI和敏捷开发
近几年炒的火热,互联网公司争先恐后,kanban
(今天公司产品说,我才知道kanban是日本人发明的)也有点大势已去,不过市场上应该还有不少公司在使用kanban。
走到了2010年,我们所看到的应该就是几个随处可见的概念了,持续交付
,DevOps
,Scrumban
(我们近几年说的真正意义上的Scrum)
1.2 瀑布模型
在这里不详细叙述,只叙述几个痛点。
- 产生客户价值周期长
- 部门、角色之间存在壁垒
- 无法及时响应需求变化
- 价值流动不可见
1.3 敏捷开发
敏捷软件开发(英语:Agile software development),又称敏捷开发,是一种能应对快速变化需求的软件开发能力。它们的具体名称、理念、过程、术语都不尽相同,相对于“非敏捷”,更强调程序员团队与业务专家之间的紧密协作、面对面的沟通(认为比书面的文档更有效)、频繁交付新的软件版本、紧凑而自我组织型的团队、能够很好地适应需求变化的代码编写和团队组织方法,也更注重软件开发过程中人的作用。
说人话,就是更注重沟通,快速产出新版本,并且更适应需求变更的的适合小团体开发的方法。
下图左方,是需求池的概念(比如jira中的backlog),比如7%的需求是现实中大量客户的需求,则我们将这7%的需求作为优先级最高的需求。
下图右方,是迭代和反馈的概念。
敏捷开发中有敏捷宣言,可以更好地阐述敏捷开发的概念。
- 个体和互动高于流程和工具(敏捷开发的站会落实了这一点)
- 工作的软件高于详尽的文档(jira、miro等更好地代替厚重的需求文档)
- 客户合作高于合同谈判(公司内各部门甩锅情况,难道不应该用合作为公司创造价值吗)
- 响应变化高于遵循计划(快速响应时长需求,而不是错过市场变化)
1.3.1 Scrum实践
下图右上方是Scrum中的角色定义。
- Product Owner(产品负责人,主要负责产品设计,需求筛选等)
- Scrum Master(敏捷主管,主要负责项目迭代跟进等)
- Scrum Team(敏捷团队,主要负责需求的研发测试部署等,包括Dev、Test、Ops等)
下图左方是用户故事
(user story
),其实就是我们传统意义上的需求,只不过以一种需求方的更委婉拟人的语气来讲述该用户人群的需求。
例如:我是一个买家,我希望我的购物车能通过价格排序,这样我就能根据我卡中的钱进行合理的消费。
下图中部,则是Scrum的核心流程。有很多公司光注重了其中的流程(比如站会),却没有得到其中的精髓。Scrum中把一个迭代叫做一个冲刺
(Sprint
),这也是很多地方把计划会议叫做冲刺会的由来,一般一个Sprint为1~4周。核心流程包括4个会议,如下所示:
- 计划会议(Product Owener、Scrum Master、Scrum Team)
- 从Backlog中按照优先级选择这个Sprint要做的User Story
- 向团队解释澄清User Story的需求,并决定是否将User Story拆解为更细粒度的
Sub Task
- 团队估计User Story的
Story Point
(用以评估story的大小,有一种好玩的方式叫做Scrum Poker
) - 团队决定是否将User Story拆分Sub Task来进行跟踪
- 决定这个Sprint的目标和交付的User Story
- 每日站会(Product Owener(可选)、Scrum Master、Scrum Team)
- 站会维持在15分钟以内,分早晚
- 团队成员讲述围绕3点:我做了什么,我将要做什么,我遇到什么困难
- 每人陆续进行讲述,为了快速响应,维持最新消息,包括需求调整等
- 以及进行高效沟通,传递信息,拒绝信息发散
- 确定相关问题后,团体相关人员小范围讨论
- 评审会议(Product Owener、Scrum Master、Scrum Team)
- 类似于传统意义上的验收阶段
- 介绍Sprint结果,按User Story顺序演示新功能
- 回答相关人员对展示的疑问,并记录其所期望的更改,收集反馈
- 如果遇到一些还没解决的障碍,则将障碍加入障碍Backlog
- 以User Story作为是否成功交付的标准来评价任务完成情况
- 回顾会议(Scrum Master、Scrum Team)
- 回顾这个Sprint,收集Sprint的相关数据
- 产生见解,多问为什么,找到各个方面的优缺点,进行复盘分析
- 进行头脑风暴分析解决方案,投票选出下期的改进项
- 探索提高效率和质量的方式
1.3.2 极限编程(XP)实战
极限编程
是敏捷开发中最具成效的几种方法之一,如同其他敏捷方法,它和传统方法的本质不同在于它更强调可适应性能性以及面临的困难。
它的基础和价值观是:
- 交流(加强交流,解决信息不同步导致的问题)
- 朴素(秉持最小可用,勤于迭代,不做拍脑袋的无用功扩展)
- 反馈(多接受反馈,以进行快速调整修改)
- 勇气(在该重构时重构,当状态不对时,放弃思考,调整状态后重新思考)
它认为任何一个软件项目都可以从四个方面入手进行改善:加强交流;从简单做起;寻求反馈;勇于实事求是。
下图是极限编程的13个最佳实践。
1.3.3 思考自己的团队是不是敏捷
问问自己下面的几个问题:
- 成员是否氢气的知道团队的目标?
- 成员是否可以预测结果并且充满信心?
- 成员是否主动做事并且为此负责?
- 成员是否愿意持续改进团队?
如果你对上述几个问题的回答时肯定的,那么恭喜你,你们的团队是关注发展的敏捷团队,如果你对上述问题有部分或全部否定,那么你可能需要调整你的团队,你们只不过是在关注敏捷的形式,而没有精髓。
1.4 DevOps模型
DevOps
是一种开发、测试和运维之间文化沟通,通过自动化的方式来进行软件交付和架构变更的流程,使构建、测试、发布软件能更快捷、频繁、可靠。它的出现是因为软件逐渐的认识到,开发、测试和运维的紧密合作可以更好的交付软件产品和服务。
下图是DevOps的标准化流程,通过建立一个完备的团队来建立一条IT服务供应链,通过自动化实现高效率交付,不固定需求管理、工具链等,只专注于持续的稳定的价值交付
2 三部工作法
2.1 第一工作法(工作流)
这一步工作法是关于从开发到运再到客户的自左向右
的工作流
2.1.1 定义工作流
下图展示了自循环工作流的流程,其中前4项属于Dev范畴,后4项属于Ops范畴:
- plan(计划)
- code(编码)
- build(构建)
- test(测试)
- release(发布)
- deploy(部署)
- operate(操作)
- monitor(监控)
2.1.2 工作流实践
下图左方是我们针对上面的工作流的一种实践,是一种工作日志的方式,这种工作方式同样适用于敏捷开发(jira中的工作日志),其实DevOps的本质就是敏捷开发。
- 过程名称
- 输入
- 输出
- 工具
- DoD(Definition of Done,完成的定义,可以理解为完成的标准)
- 平均前置任务等待数(完成当前任务,依赖等待了多少任务)
- 手工比例(手工操作的步骤占据了总量多少)
- %C/A(返工指标,完成时间/总花费时间,总花费时间=完成时间+修复时间)
- 处理时间PT(真正处理该任务所花费的时间)
- 流动时间FT(从接收到需求到需求完成所花费的时间)
通过上方工作日志所记录的数据,我们可以对我们的工作进行阶段性的分析,比如:
平均前置任务等待数
可以用来判断我们的任务分配是否合理手工比例
可以用来提示我们是否需要使用自动化来优化流程%C/A
可以用来判断一个人的工作质量是否需要优化- 等等
2.1.3 版本和分支策略
2.1.3.1 普通版本管理
- 新需求拉一条新的分支进行开发,命名
xxx feature branch
- 新bug拉一条新的分支进行修复,命名
xxx fix branch
- master分支保持永远可构建成功状态
- 每当新需求或新bug完成合并到主分支,触发主分支的pipeline构建流程
2.1.3.2 GitFLow实践
这种方案是基于GitFLow
的标准来进行版本控制的。
该方案采用develop
、feature
、hotfix
、release
、prod
等分支进行实践,比较适合版本并存的团队
- develop:主开发分支,包含所有发布到下一个release的代码
- feature:新功能开发分支,最后会合并回develop分支
- hotfix:prod发现bug时,用来热修复分支,最后会合并回develop分支
- release:发布版本时,基于develop创建的分支
- prod: 用于发布到生产环境的代码的分支,只能合并不能修改。
这种方式很灵活,代码隔离也很好,只是过于繁琐。
2.1.3.3 tag版本实践
该方案是基于版本打tag的方式来进行版本控制的。
该方案采用master
、feature
、fix
等分支进行实践,比较适合小版本滚动升级团队
- master:主分支,该分支不允许修改,只允许合并,该分支永远保持可构建状态,发布时通过tag来进行版本发布
- feature:新功能开发分支,最后会合并回master分支
- fix:线上发生bug后,用于修复的分支最终会合并到master分支
这种方式很简单,但是对多环境的支持不是很好。
我们推荐使用GitFlow+Tag的方式来进行版本控制。以触发多环境下的pipeline自动化流程。
2.1.4 规划流水线
这里主要是我们的CI/CD的流水线的结构,可已使用Jenkins
的pipeline也可以使用Gitlab
的pipeline。
这里链接下之前写的Jenkinsfile教程(Jenkins Pipeline 教程)
自动:
- 代码检测(推荐使用SonarQube,教程)
- 单元测试(很重要,java可使用junit,其他的未调研)
- 制品(过去的jar包,如今的docker image)
- 集成测试(很重要,属于自动化测试中的一个重要环节)
- 性能测试(大部分场景下可能不会每次都做)
- 安全测试(跟性能测试差不多)
- 部署预发布(这里泛指线上以下的所有环境)
手动:
- 验收测试
- 部署线上
2.1.5 Scrum+XP+DevOps流程
看过了上面的部分,你一定嗤之以鼻,因为下面的图其实就是上面的Scrum图+Pipeline的图。其实下面你的这张图才能真正意义上的指导我们如何在工作中实践Scrum+DevOps。我将下面的图分层了4块,让我们一起看看下面的图吧
Scrum+XP流程
第一部分的需求池的这个概念我们在上方的Scrum中已经看到了,在这里不做详细解释,如果记不太清了,可以回到上方#1.3.1 进行复习。
第二部分的Scrum流程需要我们再来回顾一下,一个迭代中的4个会议还记得吗,不记得就回去看看吧,通过小迭代来进行可持续的速度小型发布,其中要落实XP的13个实践,包括集体所有权、简单设计、重构等等。
CI/CD
通过代码版本控制来触发我们的的CI和CD的构建。
通过发布编码标准,测试驱动开发,代码riew等来提升我们的代码质量以进行优质的代码持续集成。
在持续集成之后,迎面而来的是构建、测试、部署,这几个步骤的才是真真正的表现我们的团队的持续交付的质量。
在3和4两个步骤,我们会看到下方有包含对号和错号的表格,这个我们会在接下来的地方讲到,这个是个CI/CD反馈表,用来表示我们某个阶段的CI/CD的缩略情况,可以通过这个反馈来进行相关的分析。
2.2 第二工作法(反馈流)
2.2.1 持续反馈
下方的图是CI/CD的持续反馈图,可能做过Jenkins的Pipeline的小伙伴已经能看出它了。
顶部的“表头”其实就是我们的Pipeline的流程,而每一行是我们的历史构建记录,通过这张图我们可以清洗的看到我们在某一阶段的pipeline的执行情况,就可以看到我们在哪一个节点发生错误的情况比较高。
在这个流程中最重要的就是测试部分,如果我们的团队对包括单元测试在内的测试环节如果不是很重视,那这个反馈将对我们的团队毫无意义。
在DevOps中,我们推崇测试驱动开发,通过先写单元测试,集成测试等用例来驱动我们的开发进行编码。
2.2.2 查看在制品
这里的制品的含义就是我们所构建的,比如java的jar,golang的native,docker的docker image等。
通过DevOps的反馈,我们可以查看制品所在的story的目前阶段
2.3 第三工作法(学习)
2.3.1 不断尝试和重复学习
让我们再来看下面这张图,对比上面的那种图,在我们的编码、测试、部署三个阶段多了个小锤子的标志。
这里的编码、测试、部署,分别代表着开发、测试、运维,三个岗位需要不断的尝试、配合和重复学习来让这条IT服务供应链更快速更稳定更自动化,让信息反馈更精准、更全面的覆盖到整个服务生命周期。
2.3.2 测试四象限
首先画一个由x轴支持评价和y轴业务技术导向组成的四象限,我们将我们DevOps中的所有种类的测试流程放入其中,来将每一个测试落实在一个二维区间内,再在每一种测试上标识一个工作投入程度,组成下面的图。
我们之前提过,在XP极限开发中,我们推崇测试驱动开发,因为测试驱动开发可以让我们在开发之前更加深入的理解业务,并且基于接口定义程序,更好的组织我们的软件架构。
所以下图是我们的测试流程应在我们的工作中所占有的工作比例,如果所有的工作经历为100%的话,那么6颗星则为60%,每颗星占据10%。
良好的开发习惯,和标准的测试流程可以让我们的代码质量更上一层楼。
2.3.3 运维四象限
上面我们说了测试的四象限,这里我们说说运维的四象限,我们以紧急性为x轴,重要性为y轴,这个四象限其实是很多工种的人都会使用到的,会将我们每天的任务放到里面,用来确定任务的优先级,我们知道基于这种四象限,我们的优先级会有下方四种:
- 紧急且重要
- 紧急不重要
- 重要不紧急
- 不重要不紧急
我们将运维的工作放在上面,如果大部分的工作都落实在紧急且重要的第一象限上的话,那么说明我们的DevOps流程是有问题的,比如我们的运维的大部分精力都在每天的线上紧急修复之类的任务,就说明我们的开发和测试的质量是有问题的。
运维的工作不应该重点在右方,而应该重心左移,偏向于重要不紧急,多做一些规划性的工作,比如从传统部署方式转向k8s容器编排等工作。
3 工程化指南
3.1 实践整合
在上面我们将上面说讲述的知识合并起来,组成我们真正在工作中实践所要用到的东西。Scrum+XP+DevOps
3.2 建立工程师文化模型
小团队内部要做到:
- 可视化面板和主动领取任务(信息扁平化,加速效率,共同目标帮助队友完成任务)
- 成为用户故事的负责人(每个人都要有主人翁意识,认为自己是UserStory的主人)
- 20%的非功能性需求(给开发一点非业务的技术需求,提升多巴胺,产生乐趣)
- 合入主干的代码需要审核(合代码需要组内review,降低代码出问题的概率)
- 周期性的技术分享(每个人都要分享,对自己所学到的东西进行沉淀,同时也吸取组内其他人的分享)
公司级别:
- 举办黑客马拉松(选择一个主题,让公司的开发进行参与,进行开发,讨论,提升技术激情)
- 举办技术沙龙
3.3 目录结构
定义每种语言的标准的目录结构,比如下方的目录结构就是Node.js的标准目录结构,我将一些语言级通用的结构用红框画了出来。
- src
- test
- .env
- Compilefile
- Dockerfile
- Jenkisnfile
3.4 版本控制规划
下图使用的是基于Tag的版本控制,我这里看推荐使用GitFLow+Tag的方式来进行基于Tag多多环境的版本控制的方式进行构建使用。
3.5 快速失败的流水线
下图中的快速失败的概念是指当pipeline中某一节点未达到通过的要求,则不再运行之后得节点,以当前节点的失败为整个pipeline的失败。
像jenkins原生就兼容快速失败。下图是jenkins最新的Blue Ocean界面,很友好的。
3.6 可视化反馈平台
在第二工作法中,我们学习到了反馈工作流,如果使用jenkins构建pipeline的时候,我们就可以通过jenkins原生支持的可视化结果来了解到我们近期的CI/CD情况。
3.7 持续改进
在我们的产品设计中,有一个MVP
的概念,它的意思是最小可行产品
,这个概念来自于《精益创业:新创企业的成长思维》这本书中,书中提倡首先定义一个面向市场的最小可用的极简原型产品,然后再不断的试验和学习中,以最小的成本和最有效的方式来验证产品是否符合用户需求,灵活调整方向,以达到“快速失败,廉价失败”的方式来验证产品是否符合市场需求。这是一种不断学习,挖掘用户需求,迭代优化产品的方式。
我们对待我们的团队,其实也要像对待我们的产品一样,不停地学习,不停地尝试,不停地优化,这样让我们的团队快速成长,要允许我们的团队犯错(但是不能重复掉进相同或相似的坑中)。
3.8 更多的质量保证
3.8.1 CI/CCD
通过良好的测试+自动化流水线来提高我们的代码的质量
在部署前:
- 集成测试
- 性能测试
- 安全性测试
部署后测试:
- 自动化测试
- 验收测试
3.8.2 环境与配置管理
通过配置中心来区别应用在各个环境中的配置,以防止出现踩到带着开发测试的配置上线的这种老旧坑。
3.8.3 制品库管理
制品库推荐也同样隔离开,预发和生产使用同一个制品库,开发和测试使用同一个制品库,在两个制品库之间,需要人为来审核和同步。
3.8.4 流程控制
这里的流程控制主要指的是CI/CD的流程控制。因为我们都知道jenkins单节点在同一时间自由一个pipeline能构建,也就是说单节点jenkins不能多条pipeline并发构建。所以我们需要搭建分布式的jenkins集群,来对pipeline的构建进行调度。
另一种更好的方式就是通过gitlab或其他支持并发pipeline构建的工具进行构建。
下方的pipeline中在测试环节分成了三个分支,这个特性我们再使用jenkins的pipeline时是有完美支持的,名字叫并行流,是根据条件来判断三个分支是否进行的。
3.8.5 数据收集和监控
我们的服务上线后,我们需要使用两种方式来确保我们线上使用的服务能够健康的提供服务。
- 日志
- 监控
通过采集我们的线上的服务的日志,来对线上日志进行分析,达到实时监控服务健康情况的需求。这里推荐使用市场较为开放通用的开源方案ELK
我们同时还要对我们的中间件,流量,物理硬件等进行相关监控,以确定基础环境的实时监控情况,这里我推荐使用Prometheus+Granfa进行监控。
3.8.6 容灾
当我们的应用日益复杂且用户量逐渐提高后,我们需要对我们的服务进行容灾配置以及周期醒的混沌工程演练。我们的服务应该像下图一样逐渐进阶为可用性更高的部署方式。
3.8.7 紧急事件处理
我们永远都不能问心无愧的拍着胸口说我们的服务非常的问题,可用性能达到100%。因为100%是我们的服务的可用性极限,就算我们的服务可用性做到6个9,8个9,但是永远也达不到100%,永远只能无限的趋近于100%。因为我们永远都没办法避免黑天鹅事件。但是我们能做的是出现黑天鹅事件后,我们要快速响应,将我们的损失降到最低。下面就说说当出现线上故障的时候,我们应该怎么做才能更好的减少我们的损失。
事故发生前(凡事有预案):
- 提前准备可能出现的事故
- 全员参与容灾演练
- 多做混沌工程,提高服务的可用性和健壮性
事故发生时(先通报,后处理):
- Ops和Dev相互通报
- Ops和Dev各自向上级汇报
- 主管决定是否继续向上级汇报
事故处理中(先止损,后查因):
- 是否有处理预案
- 有预案,Ops主管10分钟内做回滚决定
- 无预案,Ops主管和Dev主管决定回滚和补救方案
事故处理后(反思,定级):
关于Scrum+XP+DevOps的学习的更多相关文章
- XP+devOps开发模式与scrum敏捷开发对比,docker虚拟化
XP+devOps开发模式与scrum敏捷开发对比,docker虚拟化 我们现在用的就是典型的XP+devOps模式,已经放弃scrum了 现在还很多公司弄docker虚拟化docker非常复杂,当然 ...
- 团队转型,Scrum与DevOps要如何取舍?
团队在践行敏捷的过程中,会有多种选择:Scrum.XP.Kanban.Crystal.精益生产.规模化敏捷等,其中最流行的敏捷开发方法当属Scrum.正因如此,大部分人对其产生了刻板印象:认为敏捷就是 ...
- 认识Agile,Scrum和DevOps
If everything's under control you are going too slow. 当今的开发,要求faster and faster.所以我们要Agile,become Ag ...
- scrum vs devops vs sre
DevOps&SRE 超越传统运维之道[北京站] IT大咖说 - 大咖干货,不再错过 http://www.itdks.com/eventlist/detail/908
- 系统的学习Devops
系统的学习devops 1. 学习一门编程语言 Java python JavaScript 2.了解不同的操作系统概念 线程和并发,套接字,I/O管理,虚拟化,内存存储和文件系统 3.掌握终端生存大 ...
- 敏捷和DevOps:是敌是友?
DevOps是敏捷在软件开发团队的另一应用.那么相比之下,哪个更胜一筹? 一边,有业界认可的scrum master,它的朋友极限编程者,以及由其衍生的 LeSS.SAFe.DAD等,是敏捷. 另一边 ...
- 深入思考软件工程,开启 DevOps 之旅
20 世纪 60 年代,软件开始脱离硬件,逐渐成为一个独立产业.至今,软件开发过程从瀑布模型.CMM/CMMI,到 20 年前敏捷的诞生,再到今天 DevOps 的火热,一代代软件人在思考和探索,如何 ...
- 适合码农工作时玩的游戏:Scrum
适合码农工作时玩的游戏:Scrum 昨天遇到一个来自微软的面试者,在面试的最后,我简单介绍了一下我们团队使用一周一次的 Scrum 来做项目管理.他回答说:” 我在微软也用 Scrum,不过我们一周两 ...
- 20162311 实验三 敏捷开发与XP实践 实验报告
20162311 实验三 敏捷开发与XP实践 实验报告 实验内容 一.研究学习IDEA中的Code菜单 使用Code ->Reformate Code功能将以下代码格式化 public clas ...
随机推荐
- [kuangbin带你飞]专题九 连通图B - Network UVA - 315
判断割点的性质: 如果点y满足 low[y]>=dfn[x] 且不是根节点 或者是根节点,满足上述式子的有两个及其以上. 就是割点 如果是起点,那么至少需要两个子节点满足上述条件,因为它是根节点 ...
- centos 磁盘挂载
1.更改磁盘格式 fdisk -l fdisk /dev/vdb mkfs.xfs /dev/vdb1 mkfs.xfs /dev/vdb1 -f 2.查看UUID blkid 3.挂载文件夹 vim ...
- H3C 数据封装与解封装
- 洛谷P1310 表达式的值 题解 栈/后缀表达式的应用
题目链接:https://www.luogu.org/problem/P1310 本题涉及算法:栈.前缀表达式转后缀表达式,动态规划思想. 这道题目我思考了好长时间,第一时间让我做的话我也做不出来. ...
- PHP服务器端API原理及示例讲解(接口开发)
http://www.jb51.net/article/136816.htm 下面小编就为大家分享一篇PHP服务器端API原理及示例讲解(接口开发),具有很好的参考价值,希望对大家有所帮助 相信大家都 ...
- [转]Spring历史版本变迁和如今的生态帝国
前两篇: 为什么要有Spring? 为什么要有Spring AOP? 前两篇从Web开发史的角度介绍了我们在开发的时候遇到的一个个坑,然后一步步衍生出Spring Ioc和Spring AOP的概念雏 ...
- Json介绍与Ajax技术
AJAX AJAX准备知识:JSON 什么是 JSON ? JSON 指的是 JavaScript 对象表示法(JavaScript Object Notation) JSON 是轻量级的文本数据 ...
- Codeforces Round #183 (Div. 2)
A. Pythagorean Theorem II 暴力,\(O(n^2)\). B. Calendar 每个日期计算到0年1月1日的天数,相当于转化成前缀和形式. 闰年数计算\[\lfloor\fr ...
- java 泛型接口和方法
java5后,可以声明泛型接口,声明方式和声明泛型类是一样的. public interface IDAO<T>{} 泛型接口子类有两种方式: 直接在子类后申明泛型: 在子类实现的接口中给 ...
- SNOI2019
题解: t1: 想了一会才会.. 以为是啥最小表示法之类的..然后这个我又不会 其实只要考虑一下a[i],a[i+1]之间的大小关系就行了 t2: 好像和题解不太一样.. 我的做法比较麻烦.. 枚举A ...