kid’s gliper
思路:
因为方向有两种选择,所以从小到大和从大到小都要搜一遍。
最长上升子序列的模板中,很重要的就是f[i] = 1;
时间复杂度 O(n2)*o(k)
#include <iostream>
#include <cstring>
using namespace std;
const int N = 105;
int f[N],dp[N];
int a[N];
int main() {
int k;
cin>>k;
while(k--){
int n;
scanf("%d",&n);
memset(f,0,sizeof f);
memset(a,0,sizeof a);
memset(dp,0,sizeof dp);
for (int i = 1; i <=n ; ++i) {
scanf("%d",&a[i]);
}
f[1] =1;
dp[1] = 1;
for (int i = 2; i <= n; ++i) {
f[i] =1;
for (int j = 1; j <i; ++j) {
if(a[i]<a[j])
f[i] = max(f[i],f[j]+1);
}
}
for (int i = 2; i <= n; ++i) {
dp[i] =1;
for (int j = 1; j <i; ++j) {
if(a[i]>a[j])
dp[i] = max(dp[i],dp[j]+1);
}
}
int maxx = -0x3f3f3f3f;
for(int i = 1;i<=n;i++){
maxx = max(maxx,f[i]);
maxx = max(maxx,dp[i]);
}
cout<<maxx<<endl;
}
return 0;
}
登山
思路:
从左向右求一次上升f[i],从右向左求一次上升g[i],然后找到f[i]+g[i]-1最大的点即可
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e3+10;
int f[N],dp[N];
int a[N];
int main() {
int n;
cin>>n;
// memset(f,1,sizeof f);
memset(a,0,sizeof a);
// memset(dp,1,sizeof dp);
for (int i = 1; i <=n ; ++i) {
scanf("%d",&a[i]);
}
for (int i = n; i >=1; --i) {
f[i] = 1;
for (int j = n; j >i; --j) {
if(a[i]>a[j])
f[i] = max(f[i],f[j]+1);
}
}
for (int i = 1; i <= n; ++i) {
dp[i] = 1;
for (int j = 1; j <i; ++j) {
if(a[i]>a[j])
dp[i] = max(dp[i],dp[j]+1);
}
}
int maxx = -0x3f3f3f3f;
for(int i = 1;i<=n;i++){
maxx = max(maxx,dp[i]+f[i]-1);
// cout<<dp[i]<<' '<<f[i]<<endl;
}
cout<<maxx<<endl;
return 0;
}
合唱队列
思路:登山问题,但是求出来的是队列剩下的人。ans = n-maxx
代码:
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e3+10;
int f[N],dp[N];
int a[N];
int main() {
int n;
cin>>n;
// memset(f,1,sizeof f);
memset(a,0,sizeof a);
// memset(dp,1,sizeof dp);
for (int i = 1; i <=n ; ++i) {
scanf("%d",&a[i]);
}
for (int i = n; i >=1; --i) {
f[i] = 1;
for (int j = n; j >i; --j) {
if(a[i]>a[j])
f[i] = max(f[i],f[j]+1);
}
}
for (int i = 1; i <= n; ++i) {
dp[i] = 1;
for (int j = 1; j <i; ++j) {
if(a[i]>a[j])
dp[i] = max(dp[i],dp[j]+1);
}
}
int maxx = -0x3f3f3f3f;
for(int i = 1;i<=n;i++){
maxx = max(maxx,dp[i]+f[i]-1);
// cout<<dp[i]<<' '<<f[i]<<endl;
}
cout<<maxx<<endl;
return 0;
}
友好城市
思路:
观察两岸的城市,如果按照一边作为基准来选择,那么能选择的对面的城市是一个上升子序列,所以先排序再升。
代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 5e3+10;
pair<int,int> a[N];
int f[N];
int main() {
int n;
cin>>n;
for(int i = 1;i<=n;i++)
scanf("%d%d",&a[i].first,&a[i].second);
sort(a+1,a+1+n);
for(int i = 1;i<=n;i++){
f[i] = 1;
for(int j = 1;j<i;j++){
if(a[i].second>a[j].second){
f[i] = max(f[i],f[j]+1);
}
}
}
int ans = 0;
for(int i = 1;i<=n;i++)
ans = max(ans,f[i]);
cout<<ans;
return 0;
}
最大上升子序列和
思路:f[i]表示当前数的最大上升子序列最大和,将每次f[i]+1->f[i]+a[i]即可
代码:
#include <iostream>
using namespace std;
const int N = 1e3+10;
int f[N];
int a[N];
int main() {
int n;
cin>>n;
for(int i = 1;i<=n;i++){
cin>>a[i];
f[i] = a[i];
}
int maxx = 0;
for(int i = 1;i<=n;i++){
for(int j = 1;j<i;j++){
if(a[j]<a[i])
f[i] = max(f[i],f[j]+a[i]);
}
maxx = max(maxx,f[i]);
}
cout<<maxx;
return 0;
}
拦截导弹
思路:
第一问:最长不上升子序列
第二问:最长上升子序列(其实是求最多的不上升子序列组)
代码:
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e3+10;
int a[N];
int f[N];
int g[N];
int main() {
int n = 0;
while(cin>>a[n]) n++;
int ans = 0;
for(int i = 0;i<n;i++){
f[i] = 1;
for(int j = 0;j<i;j++){
if(a[j]>=a[i])
f[i] = max(f[i],f[j]+1);
}
ans = max(ans,f[i]);
}
int maxx = 0;
for(int i = 0;i<n;i++){
g[i] = 1;
for(int j = 0;j<n;j++){
if(a[i]>a[j])
g[i] = max(g[i],g[j]+1);
}
maxx = max(maxx,g[i]);
}
cout<<ans<<endl;
cout<<maxx;
return 0;
}
导弹防御系统
思路:
这一道题目无法简单dp完成,因为每一个最长子序列的方向都不确定,不能由两次最长子序列的代码计算出结果,所以需要直接进行暴力搜索dfs,保存每次的up和down的组数,然后进行贪心。
代码:
#include <iostream>
using namespace std;
const int N = 55;
int a[N];
int up[N],down[N];
int ans;
int n;
void dfs(int k,int su,int sd){
if(sd+su>=ans) return ;
if(k==n){
ans = su+sd;
return ;
}
int x = 0;
//先算一下严格上升的分组
while(x<su&&up[x]>=a[k]) x++;
int t = up[x];
up[x] = a[k];
if(x<su){
dfs(k+1,su,sd);
}
else{
dfs(k+1,su+1,sd);
}
up[x] = t;
//再计算严格下降的分组
int i = 0;
while(i<sd&&down[i]<=a[k]) i++;
t = down[i];
down[i] = a[k];
if(i<sd){
dfs(k+1,su,sd);
}
else
{
dfs(k+1,su,sd+1);
}
down[i] = t;
}
int main() {
while(cin>>n,n){
for(int i =0;i<n;i++){
cin>>a[i];
}
ans = n;
dfs(0,0,0);
cout<<ans<<endl;
}
return 0;
}
最长公共上升子序列
思路:
f(i,j): a的前i个数字和b的前j个数字,且由bj为结尾的最大公共上升子序列
状态转移
当ai不等于bj时,f[i][j]=f[i-1][j]
当ai等于bj时,要去求解所有在b中上升的子序列的最大值
再进行优化即可
暴力代码:
#include <iostream>
using namespace std;
const int N = 3e3+10;
int n;
int a[N],b[N];
int f[N][N];
int main() {
cin>>n;
for(int i = 1;i<=n;i++)
scanf("%d",&a[i]);
for (int i = 1; i <= n; ++i)
scanf("%d",&b[i]);
for(int i = 1;i<=n;i++){
for(int j = 1;j<=n;j++){
f[i][j] = f[i-1][j];
if(a[i]==b[j]){
f[i][j] = max(f[i][j],1);
for(int k = 1;k<j;k++){
if(b[k]<b[j])
f[i][j] = max(f[i][j],f[i][k]+1);
}
}
}
}
int res = 0;
for(int i = 1;i<=n;i++)
res = max(res,f[n][i]);
cout<<res;
return 0;
}
优化代码:
#include <iostream>
using namespace std;
const int N = 3e3+10;
int n;
int a[N],b[N];
int f[N][N];
int main() {
cin>>n;
for(int i = 1;i<=n;i++)
scanf("%d",&a[i]);
for (int i = 1; i <= n; ++i)
scanf("%d",&b[i]);
for(int i = 1;i<=n;i++){
int maxx = 1;
for(int j = 1;j<=n;j++){
f[i][j] = f[i-1][j];
if(a[i]==b[j])
f[i][j] = max(f[i][j],maxx);
if(b[j]<a[i])
maxx = max(maxx,f[i][j]+1);
}
}
int res = 0;
for(int i = 1;i<=n;i++)
res = max(res,f[n][i]);
cout<<res;
return 0;
}