【I - 最少拦截系统】

博客围绕一道经典动归题目展开,引出Dilworth定理和LIS。介绍了Dilworth定理包含的偏序集、链、反链等知识及两个对偶定理,给出将其实体化的公式。还提供了本题的三种解法,包括利用Dilworth定理求LIS、暴力找子链,同时提及LIS的N^2和NlgN解法,并给出代码运行情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

思路:

一:
  • 这是一道经典的动归题目,经典在它可以牵引出 Dilworth定理、LIS(Longest Increasing Subsequence)

  • 先说 DilworthDilworth定理包含如下知识:偏序集、链、反链Dilworth 包括两个对偶定理:

    1. 最大长度 = 最少反链划分数
    2. 最大反链长度 = 最少划分数
  • 那如何使用该定理呢?非常简单。以本题为例:求最少不上升(>=)划分数。首先:链和反链如何定义并不需要很深的理解,我们只需要定义一种关系,在本题中,此关系为:既前且高(=),时间上不会有重叠,高度上相当于 “高于等于”;而最少划分最大长度是紧密绑定不能更改的。如此,本题即可等价于求最大上升(<)反链长度。十分对偶。

  • Dilworth定理 实体化为公式:
    设某种关系为 &,它的反关系为 %(例如 >=<,注意严格不严格的对偶)

    1. 具有&关系链 的最少划分,等于求 具有%关系链 的最大长度。
    2. 具有%关系链 的最少划分,等于求 具有&关系链 的最大长度。
  • 通过 Dilworth 公式,我们可以轻易发现本题的一种解法:直接求出所给序列的 LIS。

二:
  • 又可根据一结论:暴力具有某关系的子链,找到子链的数目即为最少的该关系的链划分数。根据本结论,可以给出本题的第二种解法。
三:
  • 我们已经知道,第一种解法(Dilworth)实质上需要求的就是 LIS。那么 LIS 又有几种解法呢?
    1. N^2:三角形动态规划。
    2. NlgN:用到二分、顶替的思想。详见再见~雨泉的BLOG。我的代码中采用了非常非常简单的,使用了STL的二分求 LIS的方法,来自BIGKAKA的BLOG感谢。

代码:

  • 三角形朴素动归,求LIS:46ms 1428kB
//46ms		1428kB


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

using namespace std;

const int maxn = 5005;

int N;
int H[maxn];
int dp[maxn];

int main(){
	while(cin>>N){
		for(int i=1;i<=N;i++)
			dp[i] = 1;
		int MAX = 0;
		for(int i=1;i<=N;i++)
			scanf("%d" , H + i);
		for(int i=1;i<=N;i++)
			for(int j=1;j<i;j++)
				if(H[i] > H[j])
					dp[i] = max(dp[i] , dp[j] + 1);
		for(int i=1;i<=N;i++)
			MAX = max(dp[i] , MAX);
		cout<<MAX<<endl;
	}
	return 0;
}
  • 特殊结论:0ms 1424kB
//0ms		1424kB


#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define INF 0x3f3f3f3f

using namespace std;

const int maxn = 5005;

int N;
int H[maxn];
bool vis[maxn];
int ans;

void INIT(){
	ans = 0;
	memset(vis , 0 , sizeof(vis));
	return ;
}

int main(){
	while(cin>>N){
		INIT();
		for(int i=1;i<=N;i++)
			scanf("%d" , H + i);
		while(true){
			int MAX = INF;
			for(int i=1;i<=N;i++){
				if(!vis[i] && MAX >= H[i]){
					MAX = H[i];
					vis[i] = true;
				}
			}
			if(MAX == INF)
				break;
			ans++;
		}
		cout<<ans<<endl;
	}
	return 0;
}
  • LIS的NlgN:15ms 1444kB
//15ms		1444kB


#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define INF 0x3f3f3f3f

using namespace std;

const int maxn = 5005;

int N;
int H[maxn];
int dp[maxn];

void INIT(){
	memset(dp , INF , sizeof(dp));
	return ;
}

int main(){
	while(cin>>N){
		INIT();
		for(int i=1;i<=N;i++)
			scanf("%d" , H + i);
		for(int i=1;i<=N;i++)
			*lower_bound(dp , dp+N , H[i]) = H[i];
		cout<<lower_bound(dp , dp+N , INF) - dp<<endl;
	}
	return 0;
}

二刷:

  • N^2:46ms 1416kB
//46ms		1416kB


#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int maxn = 30005;

int N;
int w[maxn];
int dp[maxn];

int main(){
	while(cin>>N){
		for(int i=1;i<=N;i++)
			cin>>w[i];
		int MAX = 0;
		for(int i=1;i<=N;i++){
			dp[i] = 1;
			for(int j=1;j<=i;j++)
				if(w[j] < w[i])
					dp[i] = max(dp[i] , dp[j] + 1);
			MAX = max(MAX , dp[i]);
		}
		cout<<MAX<<endl;
	}
	return 0;
}
### 最少拦截系统简介 导弹拦截问题是经典的算法设计问题之一,在计算机科学领域具有重要意义。对于所提到的导弹拦截系统的具体实现,可以采用动态规划的方法来解决这个问题[^1]。 #### 动态规划解法分析 考虑到每枚导弹的高度限制条件——即后续发射的任何一枚导弹高度均不可高于之前的一枚,这实际上是一个寻找最长不增子序列的问题。通过构建一个数组`b[]`用于记录当前可用的不同拦截系统的最高拦截高度,遍历输入数据中的每一枚导弹高度,并尝试将其加入到合适的拦截系统中去: - 如果存在某个已有的拦截系统能够满足新到来的导弹,则更新对应位置的高度; - 否则创建一个新的拦截系统并初始化其最大拦截高度为当前导弹高度; 最终得到的结果就是所需的最小拦截系统数量[^3]。 ```cpp #include <iostream> using namespace std; int main() { int n; cin >> n; int heights[n]; for (int i = 0; i < n; ++i) { cin >> heights[i]; } int dp[n], result = 0; fill(dp, dp + n, INT_MAX); for (int height : heights) { *upper_bound(dp, dp + n, height) = height; if (*max_element(dp, dp + n) != INT_MAX && *(dp + n - 1) >= height) continue; else dp[result++] = height; } cout << "Minimum number of systems required: " << result << endl; } ``` 此代码实现了上述逻辑,其中使用了C++标准库函数`upper_bound()`来进行二分查找优化,从而提高效率。需要注意的是这里的`fill()`和`*max_element()`是为了简化边界处理而引入的操作,在实际应用中可以根据具体情况调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值