最长上升子序列和数塔变形

最长上升子序列最初是求其长度,但是变形问题很多,我主要整理了求上升子序列和的最大值和数塔相关
有一个长为n的数列a0, a1, …, a(n-1)。请求出这个序列中最长的上升子序列的长度。上升子序列指的是对于任意的i<j都满足ai<aj的子序列,该问题被称为最长上升子序列(LIS,Longest Increasing Subsequence)的著名问题。
例如:给你一个序列为(1,5 ,2,6,9,10,3,15),那么它的最长上升子序列为:(1,2,6,9,10,15)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int main()
{
    int arr[500], n, dp[500], ans = -1;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
        scanf("%d", &arr[i]); /
    /* 求解最长子序列的个数的核心 */
    for(int i = 1; i <= n; i++){
        dp[i] = 1; //初始化 如果它比前面的都小,则自己先构成一个上升子序列所以长度为1
        for(int j = 1; j < i; j++){
            if(arr[j] < arr[i]) // 如果求最大下降子序列则反之
                dp[i] = max(dp[i], dp[j] + 1);
        }
        ans = max(dp[i], ans);
    }
 
    printf("最长子序列的个数为: %d", ans);
    return 0;
}

简单变形:
2958. 求上升子序列和的最大值

由非负整数 bi(0⩽i<m−1) 满足 (i<j,bi<bj)时被称为长度为 m 的上升序列。

一个长度为 n 的序列 a0,a1,…,an−1,存在多种上升子序列:

ai0,ai1,…,aik(0⩽i0<i1<…<ik<n)。

例如:序列 1, 7, 3, 5, 9, 4, 8 的上升子序列有 (1, 7)、(3, 5, 8)、(1, 3, 5, 9) 等。这些上升子序列中序列和最大为 18,为上升子序列 1, 3, 5, 9 的和。

对于给定的序列,求出上升子序列和的最大值。

#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <map>
#include <vector>
using namespace std;
int arr[5000] = { 0 };
int dp[5000] = { 0 };
int main()
{
	int T; cin >> T;
	for (int ii = 0; ii < T; ii++) {
		int n; cin >> n;
		for (int i = 0; i < n; i++)
		{
			cin >> arr[i];
			dp[i] = arr[i];//先对dp初始化 最大和就是它本身
		}
		int k;
		int maxi=arr[0];
		for (int i = 0; i < n; i++)
		{
			for (int j = 0; j <= i - 1; j++)
			{    //核心部分一样
				if (arr[i] > arr[j])
				{
					dp[i] = max(dp[i], dp[j] + arr[i]);
					maxi = max(maxi, dp[i]);
				}
			}
			
		}
		cout << "case #" << ii << ":" << endl;
		cout << maxi << endl;
	}
}

现在先主要做了数塔相关的一些简单变形:
数塔:求从上走到下,所经过点的值之和的最大值
先把塔存到一个二维数组里面,然后用另一个二维数组dp来求解
从上到下求解:dp [i] [j] 是存放的是走到点(i,j)的路径的最大值,他的前一步只能是(i-1,j)(i-1,j-1),(注意边界),所以dp [i] [j]的值就是dp [i-1] [j]和dp [i-1] [j-1]的最大值加上arr[i][j]的结果

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
int dp[105][105], arr[105][105], n, c, MAX;
int main()
{
    scanf("%d", &c);
    while(c--){
        MAX = -1;
        memset(dp, 0, sizeof(dp));
        memset(arr, 0, sizeof(arr));
        scanf("%d", &n);
        arr[0][0] = arr[0][1] = 0;
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= i; j++){
                scanf("%d", &arr[i][j]);
                arr[i][0] = 0;
                arr[i][i + 1] = 0;
            }
        }
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= i; j++){
                dp[i][j] = arr[i][j];
                dp[i][j] = max(dp[i - 1][j - 1], dp[i - 1][j]) + arr[i][j];
            }
        }
        for(int i = 1; i <= n; i++)
            MAX = max(MAX, dp[n][i]);
        printf("%d\n", MAX);
    }
}

找最小:思路一样但是一定要注意边界的处理

#include <iostream>
#include <algorithm>
#include <math.h>
using namespace std;
int arr[101][101];
int dp[101][101];

int main()
{
    int T;cin>>T;
    for(int ii=0;ii<T;ii++)
    {
        int n;cin>>n;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=i;j++)
            {
                cin>>arr[i][j];
                arr[i][j+1]=1000;arr[i][0]=1000;
                dp[i][0]=1000;dp[i][j+1]=1000;
            }
        }
        dp[1][1]=arr[1][1];
        for(int i=2;i<=n;i++)
        {
            for(int j=1;j<=i;j++)
            {
                dp[i][j]=min(dp[i-1][j],dp[i-1][j-1])+arr[i][j];
            }
        }
        int minn=dp[n][1];
        for(int i=1;i<=n;i++)
        {

            if (dp[n][i]<minn)
                minn=dp[n][i];
        }
        cout<<minn<<endl;
    }
}

变形2求和的各位的最大值,也可以理解成mod 10(M)最大,思路就是再加一维数组来存放所由可能的余数,第三维数组的大小就是从0到M-1

  1. 数塔III
    有一个由正整数组成的三角形,第一行只有一个数,除了最下行之外每个数的左下方和右下方各有一个数,从第一行的数开始,每次都只能左下或右下走一格,直到走到最下行,把沿途经过的数全部加起来。如何走,使得这个和的个位数尽量大?
#include <iostream>
#include <algorithm>
#include <math.h>
using namespace std;
int arr[101][101];
char x[101][101][10];

int main()
{
    int T;cin>>T;
    for(int ii=0;ii<T;ii++)
    {
        int n;cin>>n;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=i;j++)
            {
                cin>>arr[i][j];
                for(int k=0;k<10;k++)
                {
                    x[i][j][k]='0';//初始化表示余数从0-9都还没出现过
                }
            }
        }
        x[1][1][arr[1][1]%10]='1';//把第一个数的第三维数组索引为arr[1][1]%10的值设为1,表示余数为arr[1][1]%10出现
        for(int i=2;i<=n;i++)
        {
            for(int j=1;j<=i;j++)
            {
                if (j!=1)//边界处理
                {
                    for(int k=0;k<10;k++)
                    {
                        if (x[i-1][j-1][k]-'0')//如果某个数左上的数的余数为k出现过,那么该数的余数为(arr[i][j]+k)%10也就出现了
                        {
                            x[i][j][(arr[i][j]+k)%10]='1';
                        }
                    }
                }
                if (j!=i)//边界处理
                {
                    for(int k=0;k<10;k++)
                    {
                        if (x[i-1][j][k]-'0')//如果某个数上面的数的余数为k出现过,那么该数的余数为(arr[i][j]+k)%10也就出现了
                            x[i][j][(arr[i][j]+k)%10]='1';
                    }
                }

            }
        }
        int m=0;
        for(int j=1;j<=n;j++)
            for(int k=0;k<10;k++)
            if (x[n][j][k]-'0')
                if (m<k) m=k;
        cout<<m<<endl;

    }
}

变形3:从第一行的数开始,每次都只能左下或右下走一格,直到走到最下行,把沿途经过的数全部乘起来。如何走,使得个位数的积尽量大 ?
其实和上面 的思路一模一样,就是把arr[i][j]+k)%10中的加号改为乘号即可

变形4:从第一行的数开始,除了某一次可以走到下一行的任意位置外,每次都只能左下或右下走一格,直到走到最下行,把沿途经过的数全部加起来。如何走,使得这个和尽量大?

思路:如果在第k行走向k+1行的任意位置,其实结果就是第1行到第k行的最大值+ K+1行到第N行的最大值,第1行到第k行的求解从上到下, K+1行到第N行的最大值的求解从下到上

#include <iostream>
#include <algorithm>
#include <math.h>
using namespace std;

int arr[501][501];
int dp[501][501];
int dp2[501][501];
int main()
{
	int T; cin >> T;
	for (int ii = 0; ii < T; ii++)
	{
		int n; cin >> n;
		for (int i = 1; i <= n; i++)
		{
			for (int j = 1; j <= i; j++)
			{
				cin >> arr[i][j];
				arr[i][j + 1] = 0; arr[i][0] = 0;
				dp2[i][0] = 0; dp2[i][j + 1] = 0;
				dp[i][0] = 0; dp[i][j + 1] = 0;
			}
		}
		for (int i = 1; i <= n; i++)
			dp[n][i] = arr[n][i];
		for (int i = n - 1; i >= 1; i--)//从下到上
		{
			for (int j = 1; j <= n; j++)//注意j一定要从1到n,否则就会有情况被覆盖:如果是从1-i,那么最后数组显示的就是求解整个过程最大值的路径,而后K行的最大值的路径不一定和该路径一样,所以要到n
			{
				dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]) + arr[i][j];
			}
		}
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= n; j++)
			{
				dp2[i][j] = max(dp2[i - 1][j], dp2[i - 1][j - 1]) + arr[i][j];

			}
		int ans = 0;
		for (int i = 1; i < n; i++)//注意范围
		{
			int tem1 = 0, tem2 = 0;
			for (int j = 1; j <= i; j++)//从上到下的数组是从1-i
			{
				tem1 = max(tem1, dp2[i][j]);
			}
			for (int j = 1; j <= i+1; j++)//从下到上的是从1——i+1
			{
				tem2 = max(tem2, dp[i+1 ][j]);
			}
			ans = max(ans, tem2 + tem1);
		}
		cout << ans << endl;
	}
}

变形5:规定路径一定要经过某个点
思路:把该点的值加一个很大的数,那么路径就一定会经过它,最后结果再把这个数减去

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值