赞
踩
AcWing 895. 最长上升子序列
这是一道典型的dp例题, dp的两个重要元素:状态表示和状态计算。其中维度的选择是很关键的,要求既能够表示出转移过程中的状态,而且能够计算出结果,在此基础上,要求维度尽可能小。
我们这里可以用dp[i]来表示以第i个数结尾的数值上升的子序列的集合,属性是max;那么对于状态转移的话,我们可以这样考虑:每一个上升子序列以倒数第二个数选什么来分类。可以有以下情况,dp[i-1]=0(即当前是第一个数),a[k] (k=0,1,……i-1); 要注意这里面并不是每一个状态都存在,我们只需要把存在的状态进行转移就可。
状态转移方程还是很好推的,我们记这个状态可以由上一个状态 j 转移过来,那么本状态的max就等于 j 状态的max+1(本次状态的长度) 。如下:
dp[i]=dp[j]+1;
结合上面两部分,整个转移方程也很容易写出来啦
for(int j=1;j<i;j++)
if(a[j]<a[i]) //保证是上升子序列
dp[i]=max(dp[i],dp[j]+1);
最后,一定要记得给dp赋初值呀!
完整代码如下:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=1e6+7; ll dp[maxn],n,a[maxn]; int main(){ scanf("%lld",&n); for(int i=1;i<=n;i++) scanf("%lld",&a[i]); for(int i=1;i<=n;i++){ dp[i]=1; for(int j=1;j<i;j++) if(a[j]<a[i]) dp[i]=max(dp[i],dp[j]+1); } ll res=-1; for(int i=1;i<=n;i++) res=max(res,dp[i]); cout<<res<<endl; return 0; }
AcWing 896. 最长上升子序列 II
朴素版本的我们已经写完了,接下来就是考虑优化的问题了。感觉优化时有点贪心的思想。
举个栗子~
就拿本题的样例吧~
3 1 2 1 8 6 6
根据我们在“一”中的思想,dp[i]表示 (balabala)(忘记了的回去看一下~)
其实很容易能够知道,开始的时候,长度为1的上升子序列有两个:{3} , {1} 如果一个数能接到3的后面,也就能接到1的后面~对,就是这样!那么既然这样的话,第一个子序列就没有存在的意义啦。 因为接到1的后面更优。
简单总结就是,对于每一组相同长度的上升子序列,我们可以只记录a[i]最小的那一组,保证最优解和高效性。
同样的,我们存储一下不同长度下最小的结尾值,我们会发现结尾值和长度是正相关的。基于此性质,可以用单调队列来优化此dp。
假如数组f现在存了数,当到了数组a的第i个位置时,首先判断a[i] > f[cnt] ? 若是大于则直接将这个数添加到数组f中,即f[++cnt] = a[i];这个操作时显然的。
当a[i] <= f[cnt] 的时,我们就用a[i]去替代数组f中的第一个大于等于a[i]的数,因为在整个过程中我们维护的数组f 是一个递增的数组,所以我们可以用二分查找在 logn 的时间复杂的的情况下直接找到对应的位置,然后替换,即f[l] = a[i]。
好了代码如下
#include<bits/stdc++.h> using namespace std; const int maxn=1e6+7; int q[maxn],a[maxn],n,cnt; 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-'0';ch=getchar();} return x*f; } inline int find(int x){ int l=1,r=cnt; while(l<r){ int mid=l+r>>1; if(q[mid]>=x) r=mid; else l=mid+1; } return l; } int main(){ n=read(); for(int i=1;i<=n;i++) a[i]=read(); q[++cnt]=a[1]; for(int i=2;i<=n;i++){ if(a[i]>q[cnt]) q[++cnt]=a[i]; else{ int temp=find(a[i]); q[temp]=a[i]; } } printf("%d\n",cnt); return 0; }
欢迎来玩~
[传送门]
1017. 怪盗基德的滑翔翼 - AcWing题库
思路: 当确定完起点后,就转化成了在两个方向上分别求解LIS问题;
代码:
#include<bits/stdc++.h> using namespace std; const int maxn=110; int a[maxn],dp[maxn]; int main(){ int t;cin>>t; while(t--){ int n;cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; memset(dp,0,sizeof dp); int res=-1; for(int i=1;i<=n;i++){ dp[i]=1; for(int j=1;j<i;j++) if(a[j]<a[i]) dp[i]=max(dp[i],dp[j]+1); res=max(res,dp[i]); } for(int i=n;i>=1;i--){ dp[i]=1; for(int j=n;j>i;j--) if(a[j]<a[i]) dp[i]=max(dp[i],dp[j]+1); res=max(res,dp[i]); } cout<<res<<endl; } return 0; }
1014. 登山 - AcWing题库
思路:
本题有三个要求:1.每次所浏览景点的编号都要大于前一个浏览景点的编号。
2.不连续浏览海拔相同的两个景点
3.一旦开始下山,就不再向上走了
分析这三个要求:可以得出最后得到的子序列是先上升再下降的,那么我们可以根据峰值来进行计算。记左边的以a[k]结尾的最长上升子序列长度 max = l [ k ] , 右边的以a[k] 开始的最长下降子序列长度 max = r [ k ] , 那么可得到
dp[k]=l[k]+r[k]-1; //要除去本身
代码:
一定要记得初始化!
#include<bits/stdc++.h> using namespace std; const int maxn=1100; int a[maxn],l[maxn],r[maxn]; int main(){ int n;cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<=n;i++){ l[i]=1; for(int j=1;j<i;j++) if(a[j]<a[i]) l[i]=max(l[i],l[j]+1); } for(int i=n;i>=1;i--){ r[i]=1; for(int j=n;j>i;j--) if(a[j]<a[i]) r[i]=max(r[i],r[j]+1); } int res=-1; for(int i=1;i<=n;i++) res=max(res,l[i]+r[i]-1); cout<<res<<endl; return 0; }
482. 合唱队形 - AcWing题库
思路: 跟上题思路相同,只需最后输出总数减去最大值即可
代码:
#include<bits/stdc++.h> using namespace std; const int maxn=1100; int a[maxn],l[maxn],r[maxn]; int main(){ int n;cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<=n;i++){ l[i]=1; for(int j=1;j<i;j++) if(a[j]<a[i]) l[i]=max(l[i],l[j]+1); } for(int i=n;i>=1;i--){ r[i]=1; for(int j=n;j>i;j--) if(a[j]<a[i]) r[i]=max(r[i],r[j]+1); } int res=-1; for(int i=1;i<=n;i++) res=max(res,l[i]+r[i]-1); cout<<n-res<<endl; return 0; }
1012. 友好城市 - AcWing题库
思路: 我们把北岸排好序之后会有a[i1].n<a[i2].n,(i1<i2)a[i1].n<a[i2].n,(i1<i2);假如对于i1,i2i1,i2,它们的南岸坐标 a[i1].s<a[i2].sa[i1].s<a[i2].s ,那么就会有交叉,而所有城市的友好城市不相同,所以我们应该建造一个严格上升的子序列。(摘自大佬博客)
代码:
#include<bits/stdc++.h> using namespace std; const int maxn=5100; struct node{ int x,y; }a[maxn]; int dp[maxn],n; bool cmp(node a,node b){ return a.x<b.x; } int main(){ cin>>n; for(int i=0;i<n;i++) cin>>a[i].x>>a[i].y; sort(a,a+n,cmp); int res=-1; for(int i=0;i<n;i++){ dp[i]=1; for(int j=0;j<i;j++) if(a[i].y>a[j].y) dp[i]=max(dp[i],dp[j]+1); res=max(res,dp[i]); } cout<<res<<endl; return 0; }
1016. 最大上升子序列和 - AcWing题库
思路: 跟LIS类似的,只不过初始化要变为a[i],转移方程为
dp[i]=max(dp[i],dp[j]+a[i]);
代码:
#include<bits/stdc++.h> using namespace std; const int maxn=1100; int a[maxn],dp[maxn]; int main(){ int n;cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; int res=-1; for(int i=1;i<=n;i++){ dp[i]=a[i]; for(int j=1;j<i;j++) if(a[j]<a[i]) dp[i]=max(dp[i],dp[j]+a[i]); res=max(res,dp[i]); } cout<<res<<endl; return 0; }
#include<bits/stdc++.h> using namespace std; const int maxn=1e6+7; int n,h[maxn],dp[maxn],q[maxn]; int main(){ int x; while(cin>>h[n]) n++; int res=0,cnt=0; for(int i=0;i<n;i++){ dp[i]=1; for(int j=0;j<i;j++) if(h[i]<=h[j]) dp[i]=max(dp[i],dp[j]+1); res=max(res,dp[i]); } cout<<res<<" "; /**贪心:从前往后扫描每个数,对于每个数 情况一:如果现有的子序列的结尾都小于当前数 则创建新的子序列 情况二:将当前数放到结尾大于等于他的最小子序列后 **/ for(int i=0;i<n;i++){ int k=0; while(k<cnt&&q[k]<h[i]) k++; q[k]=h[i]; if(k>=cnt) cnt++; } cout<<cnt<<endl; return 0; }
187. 导弹防御系统 - AcWing题库
272. 最长公共上升子序列 - AcWing题库
#include<bits/stdc++.h> #define Cutele main using namespace std; 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-'0';ch=getchar();} return x*f; } const int maxn=1100; char a[maxn],b[maxn]; int dp[maxn][maxn],n,m; int Cutele(){ scanf("%d%d",&n,&m); scanf("%s%s",a+1,b+1); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ dp[i][j]=max(dp[i-1][j],dp[i][j-1]); if(a[i]==b[j]) dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1); } printf("%d\n",dp[n][m]); return 0; }
参考博客:AcWing 896. 最长上升子序列 II - AcWing
AcWing 1012. 友好城市 - AcWing
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。