动态dp法

[原题]https://www.acwing.com/problem/content/description/897/

(动态规划) O(n2)O(n2)
状态表示:dp[i]表示从第一个数字开始算,以num[i]结尾的最大的上升序列。(以num[i]结尾的所有上升序列中属性为最大值的那一个)

状态计算(集合划分):j∈(0,1,2,..,i-1), 在num[i] > num[j]时,
dp[i] = max(dp[i], dp[j] + 1) // dp[j]+1->前一个小于自己的数结尾的最大上升子序列加上自己,即+1
有一个边界,若前面没有比i小的,dp[i]为1(自己为结尾)。

最后在找dp[i]的最大值。

时间复杂度
O(n2)O(n2) 状态数(nn) * 转移数(nn)

//动态dp板子 .cpp 

#include <iostream>
#include<vector>
#include<cstdio>
#include <algorithm>
#include<cstring>

using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f

const int N = 1010;
int num[N], dp[N];

int main()
{
    int n;
    cin >> n;
    int i, j;
    for (i = 0; i < n; i++)
    {
        cin >> num[i];
    }

    int mx = 1;
    for (i = 0; i < n; i++)
    {
        dp[i] = 1;//默认为1,找不到前面的数字小于自己的时候就为1;
        for (j = 0; j < i; j++)
        {
            if (num[i] > num[j])
                dp[i] = max(dp[i], dp[j] + 1); //动态dp板子
        }
        mx = max(mx, dp[i]);
    }
    cout << mx << endl;
    return 0;
}

基于优化目的

动态dp+二分法

1.状态表示:dp[i]表示长度为i的最长上升子序列,末尾最小的数字。(长度为i的最长上升子序列所有结尾中,结尾最小min的) 即长度为i的子序列末尾最小元素是什么。

2.状态计算:对于每一个num[i], 如果大于dpcnt-1,那就cnt+1,使得最长上升序列长度+1,当前末尾最小元素为num[i]。 若num[i]小于等于dp[cnt-1],说明不会更新当前的长度,但之前末尾的最小元素要发生变化,找到第一个 大于或等于 (这里不能是大于) num[i],更新以那时候末尾的最小元素。

3.dp[i]一定以一个单调递增的数组,所以可以用二分法来找第一个大于或等于num[i]的数字。

时间复杂度
O(nlogn)O(nlogn) 状态数(nn) * 转移数(lognlogn)

//动态dp板子 .cpp 

#include <iostream>
#include<vector>
#include<cstdio>
#include <algorithm>
#include<cstring>

using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f

const int N = 1010;
int num[N], dp[N];

int main()
{
    int n,cnt=0;
    cin >> n;
    int i, j;
    for (i = 0; i < n; i++)
    {
        cin >> num[i];
    }

    dp[cnt++] = num[0];
    for (i = 1; i < n; i++)
    {
        if (num[i] > dp[cnt - 1])
            dp[cnt++] = num[i];
        else
        {
            int left = 0,right = cnt - 1;
            while (left < right)
            {
                int mid = (left + right) >> 1;
                if (dp[mid] >= num[i])
                    right = mid;
                else
                    left = mid + 1;
            }
            dp[right] = num[i];
        }
    }
    cout << cnt << endl;
    return 0;
}

[482. 合唱队形]https://www.acwing.com/problem/content/484/

#include <iostream>
#include<vector>
#include<cstdio>
#include <algorithm>
#include<cstring>

using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f

const int N = 110;
int num[N] = { 0 }, dp1[N] = {0}, dp2[N] = {0};


int main()
{
    int n;
    cin >> n;
    int i, j, k;

    for (i = 0; i < n; i++)
    {
        cin >> num[i];
    }

    for (i = 0; i < n; i++)
    {
        dp1[i] = 1;//默认为1,找不到前面的数字小于自己的时候就为1;
        for (j = 0; j < i; j++)
        {
            if (num[i] > num[j])
                dp1[i] = max(dp1[i],dp1[j] + 1); //动态dp板子
        }
    }

    for (i = n - 1; i >= 0; i--)
    {
        dp2[i] = 1;
        for (j = n - 1; j > i; j--)
        {
            if (num[i] > num[j])
                dp2[i] = max(dp2[i], dp2[j] + 1);
        }
    }
    int mx = 1;
    for (k = 0; k < n; k++)
        mx = max(mx, dp1[k] + dp2[k] - 1);

    cout << n - mx << endl;
    return 0;
}