dp小练习(1)

最长上升子序列

这道题可以用On2的dp方法去做

下面是核心代码

for(int i =1;i<=n;++i)dp[i] = 1;  //因为每一个数都可以自己是长度为1的子序列,所以先初始化成1
int ans = 0;
for(int i =1;i<=n;++i){
    for(int j =1;j<i;++j){
        if(a[i] >a[j])dp[i] = max(dp[i] , dp[j]+1); //如果后面的比前面的大
    }
    ans = max(ans ,dp[i]);  //更新最长的
}

B3637 最长上升子序列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

这是一道模板题

#include <bits/stdc++.h>
using namespace std;
const int N = 5005;
int a[N] , dp[N];
int n;
int main(){
    cin>>n;
    for(int i =1;i<=n;++i)cin>>a[i];
    int ans = 0;
    for(int i =1;i<=n;++i)dp[i] = 1;
    for(int i =1;i<=n;++i){
        for(int j =1;j<i;++j){
            if(a[j] < a[i])dp[i] = max(dp[j]+1,dp[i]);
        }
        ans = max(ans , dp[i]);
    }
    cout<<ans;
    
    
    return 0;
}

合唱队形

P1091 [NOIP2004 提高组] 合唱队形 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

这道题我们可以看出他是一个这样(“^”)的形状,也就是中点前面是上升的,后面是下降的,那么我们可以先找到每个点前面的最长上升子序列和后面的最长下降子序列,那么也就是套模板即可

for(int i =1;i<=n;++i)dpma[i] = 1 , dpmi[i]=1;

//上升
for(int i =1;i<=n;++i){
    for(int j =1;j<i;++j){
        if(a[i] >a[j])dpma[i] = max(dpma[i] , dpma[j]+1);
    }
}

//从后往前下降
for(int i =n;i>=1;--i){
    for(int j =n;j>i;-j){
        if(a[i] > a[j])dpmi[i] = max(dpmi[i] , dpmi[j]+1);
    }
}

上面是核心代码,下面给出完整的代码

#include <bits/stdc++.h>
using namespace std;
const int N = 105;
int n , res;
int a[N] ,dpma[N]  , dpmi[N];
int main(){
    cin>>n;
    for(int i =1;i<=n;++i)cin>>a[i];
    //预处理每个地方的最长递增子序列和最长递减子序列
    for(int i =1;i<=n;++i)dpma[i] = 1 , dpmi[i]=1;
    
    for(int i =1;i<=n;++i){
        for(int j =1;j<i;++j){
            if(a[j] < a[i])dpma[i] = max(dpma[i] , dpma[j]+1);
        }
    }
    
    for(int i = n;i>=1;--i){
        for(int j =n;j>i;--j){
            if(a[i] > a[j])dpmi[i] = max(dpmi[i] , dpmi[j]+1);
        }
    }
    
    for(int i =1;i<=n;++i){
        res = max(res , dpmi[i]+dpma[i] -1);
    }
    cout<<n-res;
    
    return 0;
}

拦截导弹

拦截导弹 (nowcoder.com)

dp暴力

这道题可以用暴力,但是在洛谷中会T一部分,牛客可以过,先说暴力做法。先找到最长非递增子序列,这个不多说,但是后面的那个我们需要找到最少的非递减子序列个数,应该怎么找呢?由狄尔斯沃定理(可以自行百度),最少的非递减子序列个数 = 最长上升子序列。

那么我们只需要找到最长非递减子序列和最长上升子序列就可以了。

int main() {
    int t = 1;
    int n;
    while(cin>>n){
        a[t] = n;
        t++;
    }
    t--;
    int ans1 = 0 , ans2 = 0;
    for(int i =1;i<=t;++i)dp1[i] =1  , dp2[i]=1;
    for (int i=1;i<=t;i++) {
        for (int j=1;j<i;j++) {
            if (a[i]<=a[j]) {
                dp1[i] = max(dp1[i], dp1[j]+1);
            }
            if (a[i] > a[j]) {
                dp2[i] = max(dp2[i], dp2[j]+1);
            }
        }
        ans1 = max(ans1 ,dp1[i] );
        ans2 = max(ans2 ,dp2[i]);
    }
     
    cout<<ans1<<endl<<ans2<<endl;

贪心 + 二分 优化

在最长子序列中,我们是可以用二分进行优化的,优化后复杂度是0nlogn,那么如何优化呢,我们可以用一个b数组来维护从1枚举到n中最长上升子序列的最后一个值,假设说已经遍历到了4,那么如果a5的值是大于b4的,那么我们就把b5变成a5 。 如果不满足呢?我们就把a5的值给加到b数组里面。我们可以在b数组找到第一个大于等于a5的数,在把a5这个数放在这里。

我们可以来看一下这题,我们要先找到最长非递增子序列

int len1 = 1;
dp1[1] = a[1];
for(int i =2;i<=n;++i){
    if(a[i] <= dp[len1])dp[++len1] = a[i];
    else { //因为此时 a[i] > dp1[len1]的,所以我们要找到a[i] <dp[]的第一个数
        int idx1 = upper_bound(dp1+1 , dp1+1+len1 , a[i],greater<int>())-dp1; //因为upper_bound是找第一个大于的,所以加上greater<int>()
    
        dp1[idx] = a[i];
    }
}

其次呢也是模板,但这是要找的是最长递增子序列了,upper_bound 要换成lower_bound

if(a[i] > dp2[len2])dp2[++len2] = a[i];
    else { //a[i] <= dplen2 找第一个大于等
        int idx2 =lower_bound(dp2+1 , dp2+1+len2 , a[i]) -dp2;
        dp2[idx2] = a[i];
    }

下面是完整的代码(可以过掉洛谷加强之后的数据)

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define INF 0x3f3f3f3f
#define x first
#define y second 
#define int long long
typedef pair<int,int> PII;
const int N = 1e5;
int a[N];
int dp1[N] , dp2[N];

void solve(){
  //这题的题意是先求出最长非递增子序列
  //求最少的非递增子序列 = 最长递增子序列
  int n = 1;
  while(cin>>a[n]){
    n++;
  }
  n--;
  dp1[1] =a[1] , dp2[1] =a[1];
  int len1 =1 , len2 =1;
  for(int i =2;i<=n;++i){
    if(a[i] <= dp1[len1])dp1[++len1] = a[i];
    else {//a[i] > b[len1] 找 第一个小于的数
      int idx1 = upper_bound(dp1+1 , dp1+1+len1 , a[i],greater<int>()) - dp1;
      dp1[idx1] = a[i];
    }
    if(a[i] > dp2[len2])dp2[++len2] = a[i];
    else { //a[i] <= dplen2 找第一个大于等
        int idx2 =lower_bound(dp2+1 , dp2+1+len2 , a[i]) -dp2;
        dp2[idx2] = a[i];
    }
  }

  
  cout<<len1<<endl<<len2;

}



signed main(){
    ios::sync_with_stdio(false);
    cout.tie(0);
    cin.tie(0);
    solve();
    
    return 0;
}
    

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值