rqnoj[NOIP1999]拦截导弹



此题关键在于证明最小划分链=最长子链

导弹拦截是一个经典问题:求一个序列的最长不上升子序列,以及求能最少划分成几组不上升子序列。第一问是经典动态规划,第二问直接的方法是最小路径覆盖,但是二分图匹配的复杂度较高,我们可以将其转化成求最长上升子序列,其最大值即等于不上升子序列的最小划分数。这就涉及到组合数学中偏序集的Dilworth定理。(第二问的贪心方法其实就是这个定理的证明过程)

先介绍一下偏序关系:
偏序是在集合X上的二元关系≤(这只是个抽象符号,不是“小于或等于”),它满足自反性、反对称性和传递性。即,对于X中的任意元素a,b和c,有:
自反性:a≤a;
反对称性:如果a≤b且b≤a,则有a=b;
传递性:如果a≤b且b≤c,则a≤c 。

带有偏序关系的集合称为偏序集。
令(X,≤)是一个偏序集,对于集合中的两个元素a、b,如果有a≤b或者b≤a,则称a和b是可比的,否则a和b不可比。

在X中,对于元素a,如果任意元素b,由b≤a得出b=a,则称a为极小元。
一个反链A是X的一个子集,它的任意两个元素都不能进行比较。
一个链C是X的一个子集,它的任意两个元素都可比。

下面是两个重要定理:
定理1 令(X,≤)是一个有限偏序集,并令r是其最大链的大小。则X可以被划分成r个但不能再少的反链。
其对偶定理称为Dilworth定理:
定理2 令(X,≤)是一个有限偏序集,并令m是反链的最大的大小。则X可以被划分成m个但不能再少的链。
虽然这两个定理内容相似,但第一个定理证明要简单一些。此处就只证明定理1。
证明:设p为最少反链个数
(1)先证明X不能划分成小于r个反链。由于r是最大链C的大小,C中任两个元素都可比,因此C中任两个元素都不能属于同一反链。所以p>=r。
(2)设X1=X,A1是X1中的极小元的集合。从X1中删除A1得到X2。注意到对于X2中任意元素a2,必存在X1中的元素a1,使得a1<=a2。令A2是X2中极小元的集合,从X2中删除A2得到X3……最终,会有一个Xk非空而X(k+1)为空。于是A1,A2,…,Ak就是X的反链的划分,同时存在链a1<=a2<=…<=ak,其中ai在Ai内。由于r是最长链大小,因此r>=k。由于X被划分成了k个反链,因此r>=k>=p。因此r=p,定理1得证。

回过头来看导弹拦截第二问。我们定义偏序关系≤:a≤b表示a出现不迟于b且a的值不小于b的值。这个偏序集的最长反链即最长上升子序列,它的不上升子序列是偏序集的链。由Dilworth定理可知,不上升子序列的最小划分数=最长上升子序列的长度。

p.s. 这里的贪心方法是,每次选出所有的在它前面没有大于或等于它的数作为一组。其实我们每次选的是偏序集的最小元,因此我们最终得到的答案就是上面的k。由r<=p及r>=k>=p可以得到r=k=p,因此贪心正确。
//#define LOCAL
#include <stdio.h>
#include <string.h>

#define MAXN 1000 + 10

int numbers[MAXN];
int length[MAXN];

int main()
{
#ifdef LOCAL
     freopen("input.txt", "r", stdin);
    // freopen("output.txt", "w", stdout);
#endif
    int n;
    int i, j;
    int max, sys; 
    while(scanf("%d", &n) != EOF)
    {
    	max = 0;
    	sys = 0;
    	memset(numbers, 0, sizeof(numbers));
    	memset(length, 0, sizeof(length));
    	// 数据输入 
		for(i = 1;i <= n; i++)
		{
			scanf("%d", &numbers[i]);
		}
		
		// 完成寻找最长下降子序列 
		for(i = 1; i <= n; i++)
		{
			for(j = 1; j <= i; j++)
			{
				if(length[i] <= length[j] && numbers[i] < numbers[j])
				{
					length[i] = length[j] + 1;	
				}
			}
		}
		
		for(i = 1; i <= n; i++)
		{
			if(length[i] > max)
				max = length[i];
			
		} 
		printf("%d ", max + 1);
		// 完成需要防御系统的个数
 		memset(length, 0, sizeof(length));
 		max = 0;
		for(i = 1; i <= n; i++)
		{
			for(j = 1; j <= i; j++)
			{
				if(length[i] <= length[j] && numbers[i] > numbers[j])
				{
					length[i] = length[j] + 1;	
				}
			}
		}
		
		for(i = 1; i <= n; i++)
		{
			if(length[i] > max)
				max = length[i];
		} 
	printf("%d\n", max + 1);
		
    }
    return 0;
}


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值