【[CSP-J 2022] 上升点列】

题目

[CSP-J 2022] 上升点列
题目描述
在一个二维平面内,给定 n 个整数点 (x i ,y i​ ),此外你还可以自由添加 k 个整数点。
你在自由添加 k 个点后,还需要从 n+k 个点中选出若干个整数点并组成一个序列,使得序列中任意相邻两点间的欧几里得距离恰好为 1 而且横坐标、纵坐标值均单调不减,即 x i+1 −x i =1,y i+1 =y i 或 y i+1 −y i =1,x i+1 =x i 。请给出满足条件的序列的最大长度。

输入格式
第一行两个正整数 n,k 分别表示给定的整点个数、可自由添加的整点个数。接下来 n 行,第 i 行两个正整数 x i ,y 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

思路

1.各点按x、y排序
2.状态:到达各点的最多序列
3.状态转移:到达该点的最多序列=前面所有点中的最多序列
4.转移方程:f[i][n]=max(f[i][n],f[j][k-dis(i,j)]+dis(i,j)+1)
f[当前点][借的点数]=max(f[当前点][借的点数],
f[当前点前的每个点][当前借的点数-i到j两点间需要的点数]
+
i到j两点间需要的点数
+
i点本身
)
5.三层循环
一层,遍历每个点(i)
二层,遍历i前所有点(j)
三层,遍历能借的点数0到k-dis(i,j)

图解

在这里插入图片描述

两点间欧几里得距离

在这里插入图片描述

代码

#include <bits/stdc++.h>
using namespace std;
int n,//整数点数 
	k,//可添加整数点数 
	x,y,//整数点的坐标 
	ans,//最多序列 
	f[501][101];//在借得不同数点情况下到达每个点的最多序列数 
	//f[i][x]=max(f[i][x],f[j][x-dis(i,j)]+dis(i,j)+1) 
struct point{
	int x,y;
	bool operator<(const point p2)const{
		if(x==p2.x)return y<p2.y;
		return x<p2.x;
	}
}p[501];
int dis(int i2,int i1){
	return p[i1].x-p[i2].x+p[i1].y-p[i2].y-1;//两点间加的点,不包括端点,要-1
}
void view(){
	cout<<"各点"<<endl;
	for(int i=0;i<n;i++)cout<<p[i].x<<","<<p[i].y<<endl;
}
void view2(){
	cout<<"最多序列"<<endl;
	cout<<"添加\t";for(int x=0;x<=k;x++)cout<<x<<'\t';cout<<endl;	
	for(int i=0;i<n;i++){
		cout<<p[i].x<<","<<p[i].y<<"\t";
		for(int x=0;x<=k;x++)cout<<f[i][x]<<'\t';
		cout<<endl;	
	}
	
}
int main(){
	freopen("point.in","r",stdin);
	cin>>n>>k;
	for(int i=0;i<n;i++){
		cin>>x>>y;
		p[i]=point{x,y}; 
	}
	//view();
	sort(p,p+n);
	view();
	for(int i=0;i<n;i++){//遍历所有点 
		f[i][0]=1;//初始化,每个点序列起码有1 
		for(int j=0;j<i;j++)//遍历前面的所有点 
		if(p[j].y<=p[i].y){//如果是升序(右上) 
			int m=dis(j,i);//欧几里得距离 
			for(int x=0;x+m<=k;x++)//j已经加的点+现在i到j加的点不超过k 
			f[i][x+m]=max(f[i][x+m],f[j][x]+dis(j,i)+1);
			/*
			第i点用了x+m个点后的最多序列=
			以下中最大
			本来的最多序列
			i前j点用了x个点后的最多序列,加上i到j需要的点,+i点本身 
			*/	
		}	
	}
	view2();
	for(int i=0;i<n;i++)//遍历所有点,不一定是最后一个序列最多 
	for(int j=0;j<=k;j++)//用添加点最少而序列最多的 
	ans=max(ans,f[i][j]+k-j);//在必须用的点基础上还可以用k剩余的点 
	cout<<ans;
	return 0;
}

数据

在这里插入图片描述

### 关于P8816 [CSP-J 2022] 上升问题的解法 #### 题目分析 题目要求找到给定序的一个最长上升子序(LIS),并返回其长度以及任意一种可能的具体方案。此问题可以通过动态规划的方法解决。 以下是基于动态规划的思想实现的C++代码示例: ```cpp #include <iostream> #include <vector> using namespace std; int main() { int n; cin >> n; vector<int> nums(n); for (int i = 0; i < n; ++i) { cin >> nums[i]; } // dp数组用于记录以第i个元素结尾的最长上升子序长度 vector<int> dp(n, 1); // prev数组用于回溯路径,记录前驱节 vector<int> prev(n, -1); // 动态规划求解dp数组和prev数组 for (int i = 1; i < n; ++i) { for (int j = 0; j < i; ++j) { if (nums[j] < nums[i] && dp[j] + 1 > dp[i]) { dp[i] = dp[j] + 1; prev[i] = j; } } } // 找到dp数组中的最大值及其位置 int maxLength = *max_element(dp.begin(), dp.end()); int index = max_element(dp.begin(), dp.end()) - dp.begin(); // 输出最长上升子序的长度 cout << maxLength << endl; // 使用prev数组回溯得到具体方案 vector<int> lis; while (index != -1) { lis.push_back(nums[index]); index = prev[index]; } // 将结果反转后输出 reverse(lis.begin(), lis.end()); for (size_t i = 0; i < lis.size(); ++i) { cout << lis[i]; if (i != lis.size() - 1) cout << ' '; } cout << endl; return 0; } ``` #### 代码解释 1. **输入处理** 输入数据被读取至`nums`向量中[^5]。 2. **初始化DP数组与Prev数组** `dp[i]` 表示以第`i`个元素结尾的最长上升子序的长度;`prev[i]` 记录到达`i`的最佳前驱节索引,以便后续回溯构建具体的 LIS 序[^6]。 3. **状态转移方程** 如果存在某个`j<i`使得`nums[j]<nums[i]` 并且`dp[j]+1>dp[i]`,则更新`dp[i]=dp[j]+1` 和 `prev[i]=j`[^7]。 4. **查找全局最优解** 寻找整个`dp[]` 数组的最大值作为最终的结果,并定位对应的索引位置[^8]。 5. **重建路径** 利用`prev[]` 数组逆序追踪从终回到起的过程,从而获得完整的 LIS 路径[^9]。 #### 时间复杂度 该算法的时间复杂度为 \(O(N^2)\),其中 \(N\) 是输入序的长度。这是因为两层嵌套循环分别迭代了所有的元素组合[^10]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值