信息给我的触动和如何简化题目信息
提到最长上升子序列,相信大家并不陌生,这个困扰了我很久的难题,终于在今天的课堂上解决了,刚开始听这两道题的讲解时,我如恍然大悟一般,立刻就领悟到了其中思维的奥妙,包括差分(https://blog.csdn.net/BZ_C25LX/article/details/128054469?spm=1001.2014.3001.5501),这三种题目都是用一种状态转移的思想完成的,他每次都是直接取与他离之最近的最大值,这也是最让我触动的地方,特别是第二种题目,求两个不相交的最大子序列,当刚开始做这道题的时候,我不知道如何下手,因为我并不知道如何判断是否相交,这时,我的一个同学提出了一个思路,我觉得这个思路非常的巧妙,也就是一个数组记录从前往后的值,一个数组记录从后往前的值,这样当我们求最大值时,就可以直接取一个所谓的断点,记录从前往后的数组就取断点前的最大值,记录从后往前的数组就断点后的最大值,与之相加在比较每一个断点的值即可。特别是最近,做一些有难度的题时,我们总需要寻求一种最巧妙的方法解题,不然就会TLE,说诗意一点,用普通方法做你是在做数学题,用简便方法做你是在做信息题。下面举个例子,《青蛙的约会》(https://www.luogu.com.cn/problem/P1516)这道题中有两个动态的变量,在做题时,如果我们一味地枚举或直接用数学方法做,一是很复杂,越复杂的代码约越会出错,也越难Debug,二是肯定会超时,直接这样做也就是我们刚才说的做数学题。这时,我们要寻求一种简单的方法,这也是信息的魅力所在,遇到这种问题时,我们可以采用一种化动为静的的思想,也就是“一直青蛙不动,让另一直跳”这样问题就简化了许多,加上一点扩展欧几里得算法,这道题就能很轻松的解决了。这种题做多了你也会发现他其实并不难。
最后感谢一直给我提供了灵感的老师和同学。
下面进入正题
最简单的最大子序列的和
题目描述
给定一个有n(n≥1)个整数的序列,要求求出其中最大连续子序列的和。
例如:
序列(-2,11,-4,13,-5,-2)的最大子序列和为20
序列(-6,2,4,-7,5,3,2,-1,6,-9,10,-2)的最大子序列和为16。
规定一个序列最大连续子序列和至少是0,如果小于0,其结果为0。
输入格式
第一行输入一个整数n 第二行输入n个用空格分开的整数
输出格式
输出一个整数,表示这个序列的最大连续子序列的和
样例
样例输入1:
6
-2 11 -4 13 -5 -2
样例输出1:
20
样例输入2:
12
-6 2 4 -7 5 3 2 -1 6 -9 10 -2
样例输出2:
16
分析
这道题主要就是注意几个点,连续,最大。我们可以利用一个状态转移的思想来解决这道题,也就是一定程度上的贪心思想,每一个数我们都求以他结尾的子序列的最大值,然后每一个数都关注他的前一个数的值,为了使这个数的值更大一些,我们判断:当他前一个数为非负数时,我们加上他;否则,我们可以抛弃他,也就是不管他,这样,我们就可以求出最大子段和了。在这个code实现的过程中,我们需要注意两个点,也就是边界情况和初始化(顺便提一句:大家在比赛时如果样例错的有没有检查出你的代码有什么大问题,你就需要注意一下你的边界了,不要出现花大量时间Debug,结果就是没有算边界的情况,不然会很划不来的),下面给出AC代码:
#include<bits/stdc++.h>
using namespace std;
int a[10000000],b[10000000],ma,i,n;
int main(){
ios::sync_with_stdio(false);
cin>>n;
for(i=1;i<=n;i++){
cin>>a[i];
}
b[1]=a[1];
ma=b[1];
for(i=2;i<=n;i++){
if(b[i-1]>=0){
b[i]=b[i-1]+a[i];
}
else b[i]=a[i];
if(b[i]>ma) ma=b[i];
}
if(ma<0) cout<<0;//这是这个题目中的要求,如果其他题目没有可以不需要
else cout<<ma;
}
还有就是,一定注意你的代码是否符合题目要求,比如有些题目就会要求负数输出0等,这些也是需要注意的地方,所以,不要盲目抄代码哟。
求不相交的两条子序列的最大值
题目描述
给定一串数Ai ,在这一串数中找到不相交的两个非空子段使其和最大。
输入格式
第1行:一个整数n,表明串中数的个数。
第2行至第n+1行:每行一个整数Ai。
输出格式
一个数,表示这一串数中不相交的两个最大子段的和。
样例
样例输入
10
1
-1
2
2
3
-3
4
-4
5
-5
样例输出
13
分析
这道题与上一道题目的区别就在于,本题要求两条子串不相交,这就是一大难点了,怎么保证两条子串不想交呢,其实在之前也提到过了,也就是一个数组顺序,一个数组逆序,这种方法很巧妙(你在看博客的时候一定要先自己思考,不然你直接看博客就会感觉没什么大不了的,但如果你已经仔细思考过,就算没想出来,你也会有很大的触动,慢慢地,这种能力就被培养起来了)。
所以,这道题只需要多定义一个数组,然后多写一次循环来设置断点就好了。
DP解决不连续上升问题
题目描述
给定一个整数序列A1A2A3….An。求它的一个递增子序列,使子序列的元素个数尽量多,元素不一定要求连续。
输入格式
第1行:1个整数n(1<=n<=5000),表示序列中元素的个数.
第2行-n+1行:每行1个整数x(-1000<=x<=1000),第i+1行表示序列中的第i个元素。
输出格式
第1行:1个整数k,表示最长上升子序列的长度。
第2行:k个用单个空格分开的整数,表示找到了最长上升子序列。输出的是序列中的元素,并非下标。如果有多个长度等于k的子序列,则输出最靠前的1个。
样例
样例输入
8
1
3
2
4
3
5
4
6
样例输出
5
1 3 4 5 6
分析
这道题,我们发现,他又升级了,他不仅是不连续,还要上升,还要输出序列,这时,我们就有必要拿出我们的秘密武器——DP动态规划了。
首先,我们需要沿用上一道题的思想,半贪心,不同的是,我们不能仅仅在前一个数寻求最值了,我们要全局搜索,那问题就来了,我们以什么因素决定选哪个变量呢?
1、题目要求上升,我们肯定要选比自己小的变量。
2、尽量让这个值大
千万注意
这道题不是求和了,而是求长度,所以,我们在状态转移时,每次是加1而不是加上这个数的值,下面是示意图:
在这里,我们还要考虑的一个问题就是,再上面说的两个条件中,到底那个条件优先级更高呢,当然是前者,虽然你不注意这个地方也无大碍,只不过多判断一次罢了,但是信息竞赛就是要精益求精
给个小提示
你不能在做题时只追求过样例或侥幸揣测测试点太弱,凭这样AC对你并没有好处,一定要精益求精。
下面是正确的比较方法(上面那个也是,下面这个难一些)
代码实现的话,也只需要注意我们刚才提到的那几点,在原码基础上加以修改即可。
到这里我们的三种情况就讲完了,手工绘图不易,望支持~~