【题解】【动态规划,最长上升子序列LIS】—— [CSP-J 2022] 上升点列

[CSP-J 2022] 上升点列

通往洛谷的传送门

题目描述

在一个二维平面内,给定 n n n 个整数点 ( x i , y i ) (x_i, y_i) (xi,yi),此外你还可以自由添加 k k k 个整数点。

你在自由添加 k k k 个点后,还需要从 n + k n + k n+k 个点中选出若干个整数点并组成一个序列,使得序列中任意相邻两点间的欧几里得距离恰好为 1 1 1 而且横坐标、纵坐标值均单调不减,即 x i + 1 − x i = 1 , y i + 1 = y i x_{i+1} - x_i = 1, y_{i+1} = y_i xi+1xi=1,yi+1=yi y i + 1 − y i = 1 , x i + 1 = x i y_{i+1} - y_i = 1, x_{i+1} = x_i yi+1yi=1,xi+1=xi。请给出满足条件的序列的最大长度。

输入格式

第一行两个正整数 n , k n, k n,k 分别表示给定的整点个数、可自由添加的整点个数。

接下来 n n n 行,第 i i i 行两个正整数 x i , y i x_i, y_i xi,yi 表示给定的第 i i i 个点的横纵坐标。

输出格式

输出一个整数表示满足要求的序列的最大长度。

输入输出样例

输入 #1

8 2
3 1
3 2
3 3
3 6
1 2
2 2
5 5
5 3

输出 #1

8

输入 #2

4 100
10 10
15 25
20 20
30 30

输出 #2

103

提示

【样例 #3】

见附件中的 point/point3.inpoint/point3.ans

第三个样例满足 k = 0 k = 0 k=0

【样例 #4】

见附件中的 point/point4.inpoint/point4.ans

【数据范围】

保证对于所有数据满足: 1 ≤ n ≤ 500 1 \leq n \leq 500 1n500 0 ≤ k ≤ 100 0 \leq k \leq 100 0k100。对于所有给定的整点,其横纵坐标 1 ≤ x i , y i ≤ 10 9 1 \leq x_i, y_i \leq {10}^9 1xi,yi109,且保证所有给定的点互不重合。对于自由添加的整点,其横纵坐标不受限制。

测试点编号 n ≤ n \leq n k ≤ k \leq k x i , y i ≤ x_i,y_i \leq xi,yi
1 ∼ 2 1 \sim 2 12 10 10 10 0 0 0 10 10 10
3 ∼ 4 3 \sim 4 34 10 10 10 100 100 100 100 100 100
5 ∼ 7 5 \sim 7 57 500 500 500 0 0 0 100 100 100
8 ∼ 10 8 \sim 10 810 500 500 500 0 0 0 10 9 {10}^9 109
11 ∼ 15 11 \sim 15 1115 500 500 500 100 100 100 100 100 100
16 ∼ 20 16 \sim 20 1620 500 500 500 100 100 100 10 9 {10}^9 109

1.题意解析

    只需要看一眼就知道只是一道 d p dp dp模版题。

    这道题就是最长上升子序列的升级版,不知道的请移步这里

    首先动态规划五步走

1.抽象问题:求“最长不下降子序列”的长度。

    下图就提供了一个“不下降子序列”的示例。请尝试结合题目理解。
在这里插入图片描述

2.状态:dp[i][j]代表以第 i i i个点结尾增加 j j j个点的“最长不下降子序列”的长度。

    根据问题的定义来定义就行了。

3.初始条件:dp[i][j]=j+1 i ∈ [ 1 , n ] i\in[1,n] i[1,n] j ∈ [ 0 , k ] j\in[0,k] j[0,k]

    容易知道,想要让 d p [ i ] [ j ] dp[i][j] dp[i][j]尽量的大,最直接的方法就是在前面直接加上j个点。再加上当前点,得到初始状态j+1

4,状态转移方程:dp[i][p]=max(dp[i][p],dp[j][p-add]+add+1)
    其中: i ∈ [ 2 , n ] i\in[2,n] i[2,n] j ∈ [ 1 , i − 1 ] j\in[1,i-1] j[1,i1] p ∈ [ a d d , k ] p\in[add,k] p[add,k]
    add代表由点 i i i到点 j j j需要添加的点的个数

    看完后是不是一头雾水?

    根据LIS中对方程的推导,可以发现,以第 i i i个点结尾的序列的最长长度就是前面能接上的所有点的最长长度 + 1 +1 +1

    因此,对于每一个点 i i i。直接枚举前面的所有点 j j j。如果能接得上(即保证add<ka[j].y<a[i].y(即能通过增加点使得 a [ j ] . y = a [ i ] . y a[j].y=a[i].y a[j].y=a[i].y))。

    然后更新每一个 a [ i ] [ p ] a[i][p] a[i][p]。因为前面我们已经增加了add个点了。所以p要从add开始枚举到k。这个时候,我们只剩下p-add个点了。类似于LIS,我们只需要让a[i][p]对每一个a[j][p-add]+1取最大值就行了。但直接这样写是错的。还记得这个条件的前提吗?没错,我们已经增加了 a d d add add个点。所以应该对每一个a[j][p-add]+add+1取最大值。

    最后,对于add可以这样计算:add=a[i].x-a[j].x+a[i].y-a[j].y-1;,这里先卖个关子。请读者自行思考。

2.AC代码

#include<bits/stdc++.h>
using namespace std;
#define MAXN 510
int n,k,ans,dp[MAXN][MAXN];//dp[i][j]表示在i前面增加j个点的最大序列长度 
struct dote//储存每个点的坐标 
{
	int x,y;
}a[MAXN];
bool cmp(dote a,dote b)//排序 
{
	if(a.x==b.x)return a.y<b.y;
    return a.x<b.x;
}
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)scanf("%d%d",&a[i].x,&a[i].y);
    sort(a+1,a+n+1,cmp);
    for(int i=1;i<=n;i++)//初始化 
        for(int j=0;j<=k;j++)//想要初始让dp[i][j]尽可能大,直接添加j个点
            dp[i][j]=j+1;
    for(int i=2;i<=n;i++)//从第2个点开始枚举
        for(int j=i-1;j>=1;j--)//枚举前面的每一个点
        {
        	//计算需要到达此点最少需要添加的点数
        	int add=a[i].x-a[j].x+a[i].y-a[j].y-1;
        	if(add>k||a[i].y<a[j].y)continue;//不符合条件
        	for(int p=add;p<=k;p++)//分别枚举一共添加从add到k个点
        	    dp[i][p]=max(dp[i][p],dp[j][p-add]+add+1);//状态转移方程
		}
	for(int i=1;i<=n;i++)//取每一个数的最大值
	    ans=max(ans,dp[i][k]);
	printf("%d",ans);
	return 0;
}

喜欢就订阅此专辑吧!

【蓝胖子编程教育简介】
蓝胖子编程教育,是一家面向青少年的编程教育平台。平台为全国青少年提供最专业的编程教育服务,包括提供最新最详细的编程相关资讯、最专业的竞赛指导、最合理的课程规划等。本平台利用趣味性和互动性强的教学方式,旨在激发孩子们对编程的兴趣,培养他们的逻辑思维能力和创造力,让孩子们在轻松愉快的氛围中掌握编程知识,为未来科技人才的培养奠定坚实基础。

欢迎扫码关注蓝胖子编程教育
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝胖子教编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值