经典的导弹拦截问题

题目:http://wikioi.com/problem/1044/
 
 

题意:一种导弹拦截系统的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉

敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。输入导弹依次飞来的高

度,计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统?

 

 

分析:

第一问很简单,就是求最长不上升子序列。对于第二问不能用贪心的方法来做,因为有反例:7 5 4 1 6 3 2.

我们把第二问的问题抽象出来,那就是:把一个数列划分成最少的最长不升子序列,这里我们要介绍一个很优美的定理。

 

Dilworth定理:对于一个偏序集,最少链划分等于最长反链长度。

Dilworth定理的对偶定理:对于一个偏序集,其最少反链划分数等于其最长链的长度。

 

也就是说把一个数列划分成最少的最长不升子序列的数目就等于这个数列的最长上升子序列的长度。

 


下面来说说这个定理是怎么来的:

 

偏序集的定义:偏序是在集合X上的二元关系≤(这只是个抽象符号,不是“小于或等于”,它满足自反性、反对称性和传递

性)。即,对于X中的任意元素a,b和c,有:

 

(1)自反性:a≤a;

(2)反对称性:如果a≤b且b≤a,则有a=b;

(3)传递性:如果a≤b且b≤c,则a≤c 。

 

带有偏序关系的集合称为偏序集。

 

 


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

在这个例子(反链)中元素Ri<=Rj是指(i<=j) and (ai>=aj)

 

一个反链A是X的一个子集,它的任意两个元素都不能进行比较。

一个链C是X的一个子集,它的任意两个元素都可比。

 

【定理】

在X中,对于元素a,如果任意元素b,都有a≤b,则称a为极小元。

定理1:令(X,≤)是一个有限偏序集,并令r是其最大链的大小。则X可以被划分成r个但不能再少的反链。

 

其对偶定理称为Dilworth定理:

令(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非空而Xk+1为空。于是A1,A2,…,Ak就是X的

反链的划分,同时存在链a1<=a2<=…<=ak,其中ai在Ai内。由于r是最长链大小,因此r>=k。由于X被划分成了k个反链,因此

r>=k>=p。

(3)因此r=p,定理1得证。

 

【解决】

要求最少的覆盖,按照Dilworth定理

最少链划分 = 最长反链长度

所以最少系统 = 最长导弹高度上升序列长度。

 

 

 

Dilworth定理的应用

 

题目:http://poj.org/problem?id=1065

 

题意:给定n个二元组,设第一个元素为a[],第二个元素为b[],求最少的划分数使得每一种划分中a[]和b[]不是下降序列。

 

分析:这个问题是二元组的最少链划分,那么我们以a[]为关键字大小进行排序,如果a[]中相同就按照b[]排序,根据

Dilworth定理,然后题目就变成了求b[]序列中最长严格下降子序列长度了。

#include 
#include 
#include 
#include 

using namespace std;
const int N = 5005;
const int INF = ~0U>>1;

struct Node
{
    int x,y;
};

Node a[N],t[N];
int d[N];

bool cmp(Node a,Node b)
{
    return a.x < b.x || (a.x == b.x && a.y < b.y);
}

int Binary_Search(int l,int r,int x)
{
    while(l < r)
    {
        int m = (l + r) >> 1;
        if(x <= d[m]) r = m;
        else l = m + 1;
    }
    return l;
}

int Work(Node a[],int n)
{
    d[0] = -1;
    int max = -1;
    int len = 1;
    for(int i=1;i<=n;i++)
    {
        d[len] = INF;
        int j = Binary_Search(0,len,a[i].y);
        if(j == len) len++;
        d[j] = a[i].y;
    }
    return len - 1;
}

int main()
{
    int n,T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        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++)
           t[n-i+1] = a[i];
        printf("%d\n",Work(t,n));
    }
    return 0;
}

 

题目:http://poj.org/problem?id=3636

 

题意:跟上题基本一样,只是把上题的条件由l <= l' and w <= w'改为了l < l' and w < w'。

 

分析:二者区别在于前者是大于等于关系,而后者必须是大于的。这就导致了二者排序的方式不一样,第一个参数都是从小到

大排,但是第二个参数就有所区别了。前者要求是包含等于的,所以第2个参数也是从小到大排;而后者则不包含等于所以要从

大到小排 。因为w升序,h降序可以保证w相等时,一定不会出现覆盖的情形。另外上题是求最长的严格下降子序列,而本题要

求最长的不上升子序列。所以,排序这样:

bool cmp(Node a,Node b)
{
    return a.x < b.x || (a.x == b.x && a.y > b.y);
}

 

 

题目:http://poj.org/problem?id=1548

 

题意:在一个矩形区域内,机器人从左上角出发,每次只能沿着现在位置的下方和右方移动,在移动过程中收拾垃圾,一直到区域

的右下角,现在给出所有垃圾的位置,求解最少需要多少个机器人才能将这些垃圾清理成功?

 

分析:可以利用Dilworth定理,先排序,然后求最长下降子序列。跟POJ1065基本一样。

#include 
#include 
#include 
#include 

using namespace std;
const int N = 5005;
const int INF = ~0U>>1;

struct Node
{
    int x,y;
};

Node a[N],t[N];
int d[N];

bool cmp(Node a,Node b)
{
    return a.x < b.x || (a.x == b.x && a.y < b.y);
}

int Binary_Search(int l,int r,int x)
{
    while(l < r)
    {
        int m = (l + r) >> 1;
        if(x <= d[m]) r = m;
        else l = m + 1;
    }
    return l;
}

int Work(Node a[],int n)
{
    d[0] = -1;
    int max = -1;
    int len = 1;
    for(int i=1;i<=n;i++)
    {
        d[len] = INF;
        int j = Binary_Search(0,len,a[i].y);
        if(j == len) len++;
        d[j] = a[i].y;
    }
    return len - 1;
}

int main()
{
    while(true)
    {
        int n = 0;
        int x,y;
        while(scanf("%d%d",&x,&y))
        {
            if(x == -1 && y == -1) return 0;
            if(x == 0  && y == 0)  break;
            n++;
            a[n].x = x;
            a[n].y = y;
        }
        sort(a+1,a+n+1,cmp);
        for(int i=1;i<=n;i++)
           t[n-i+1] = a[i];
        printf("%d\n",Work(t,n));
    }
    return 0;
}


 

题目:http://poj.org/problem?id=1631

 

题意:给定一个二分图,二分图左右两边的顶点数相同,有些顶点之间连有边,选出最多的边使得这个二分图中所有的边都不相交。

分析:基本上跟POJ1065和POJ3636的题目一样,转化一下就行了。

 

 

你可能感兴趣的:(动态规划)