tyvj1048 田忌赛马

描述

    中国古代的历史故事“田忌赛马”是为大家所熟知的。话说齐王和田忌又要赛马了,他们各派出N匹马,每场比赛,输的一方将要给赢的一方200两黄金,如果是平局的话,双方都不必拿出钱。现在每匹马的速度值是固定而且已知的,而齐王出马也不管田忌的出马顺序。请问田忌该如何安排自己的马去对抗齐王的马,才能赢取最多的钱?

输入格式

第一行为一个正整数n (n <= 1000) ,表示双方马的数量。
第二行有N个整数表示田忌的马的速度。
第三行的N个整数为齐王的马的速度。

输出格式

仅有一行,为田忌赛马可能赢得的最多的钱,结果有可能为负。

测试样例1

输入


92 83 71 
95 87 74

输出

200
田忌赛马(贪心+动态规划)
这个问题很显然可以转化成一个二分图最佳匹配的问题。把田忌的马放左边,把齐王的马放右边。田忌的马A和齐王的B之间,如果田忌的马胜,则连一条权为200的边;如果平局,则连一条权为0的边;如果输,则连一条权为-200的边。
然而我们知道,二分图的最佳匹配算法的复杂度很高,无法满足N=2000的要求。
我们不妨用贪心思想来分析一下问题。因为田忌掌握有比赛的“主动权”,他总是根据齐王所出的马来分配自己的马,所以这里不妨认为齐王的出马顺序是按马的速度从高到低出的。由这样的假设,我们归纳出如下贪心策略:

1、如果田忌剩下的马中最强的马都赢不了齐王剩下的最强的马,那么应该用最差的一匹马去输给齐王最强的马。
2、如果田忌剩下的马中最强的马可以赢齐王剩下的最强的马,那就用这匹马去赢齐王剩下的最强的马。 
3、如果田忌剩下的马中最强的马和齐王剩下的最强的马打平的话,可以选择打平或者用最差的马输掉比赛。

第一个贪心策略的证明:
此时田忌的所有马都赢不了齐王的马,所以无论用最慢马去输还是用最快的马去输都同样是输,而处于贪心的思想,我们应该保留相比之下更强的马,因此用最慢的马去输一定不会比用别的马去输来得劣,所以这是最优策略。
证毕。

第二个贪心策略的证明:
假设现在齐王剩下的最强的马是A,田忌剩下的最强的马是B,如果存在一种更优的比赛策略,让B的对手不是A,而使得田忌赢更多的钱的话,那么设此时A的对手是b,B的对手是a:
若b>A,则有B>a,b>A。这个结果和B>A,b>a是相同的。
若a<b≤A,则有B>a,b≤A。这个结果不如B>A,b>a来得优秀。
若b≤a≤A,则有B>a,b≤A。这个结果和B>A,b≤a是相同的。
由此可知,交换各自对手后,一定不会使得结果变劣,那么假设是不成立的。
证毕。

第三个贪心策略的证明:
因为田忌最快的马也只是和齐王的马打平,那么田忌只能选择平或输,选择平的话,当然只能用最快的马去平了;选择输的话当时是用最慢的马去输来得值得,这和第一个贪心策略的思路是一样的。
证毕。

我们发现,第三个贪心策略出现了一个分支:打平或输掉。如果穷举所有的情况,算法的复杂度将比求二分图最佳匹配还要高;如果一概而论的选择让最强的马去打平比赛或者是让最差的马去输掉比赛,则存在反例:
光是打平的话,如果齐王马的速度分别是1 2 3,田忌马的速度也是1 2 3,每次选择打平的话,田忌一分钱也得不到,而如果选择先用速度为1的马输给速度为3的马的话,可以赢得200两黄金。
光是输掉的话,如果齐王马的速度分别是1 3,田忌马的速度分别是2 3,田忌一胜一负,仍然一分钱也拿不到。而如果先用速度为3的马去打平的话,可以赢得200两黄金。

虽然因为第三个贪心出现了分支,我们不能直接的按照这种方法来设计出一个完全贪心的方法,但是通过上述的三种贪心策略,我们可以发现,如果齐王的马是按速度排序之后,从高到低被派出的话,田忌一定是将他马按速度排序之后,从两头取马去和齐王的马比赛。有了这个信息之后,动态规划的模型也就出来了!
设f[i,j]表示齐王按从强到弱的顺序出马和田忌进行了i场比赛之后,从“头”取了j匹较强的马,从“尾”取了i-j匹较弱的马,所能够得到的最大盈利。
状态转移方程如下:
f[i,j]=max{f[i-1,j]+g[n-(i-j)+1,i],f[i-1,j-1]+g[j,i]}
其中g[i,j]表示田忌的马和齐王的马分别按照由强到弱的顺序排序之后,田忌的第i匹马和齐王的第j匹马赛跑所能取得的盈利,胜为200,输为-200,平为0。
--------------分割线-----------------------------------------
 俩数组 ,排序,l1=1; l2=1; r1=n; r2=n;
( 左向右  如果  a[l1]>b[l2] 则  l1:=l1+1; l2:=l2+1; ans:=ans+200;
  右往左 如果   a[r1]>b[r2] 则  r1:=r1-1;   r2:=r2-1; ans:=ans+200;
  此时如果 a[l1]=b[r2] 则中间的数全相等,不用算了。。
     如果 a[l1]<b[r2] 则  s:=s-200;  l1:=l1+1; r2:=r2-1;)
继续() 的循环;
 证明:
   得到从左往右第一个 a[l1]<=b[l2] 和从右往左第一个 a[r1]<=b[r2] 时,
   草稿纸上演算一下就知道。。此时不管b[l2]是否等于a[l1] b[r2]是否等于a[r1]  让a[l1]和b[r2] 比赛  总是最好情况之一; 
-------------------------rzy------------
稍作简单分析
既可以知道如果自己最强的马打不过齐王的马就派出自己最弱的马
如果打的过的话就去把它灭了
这样一分析,就可以知道 只能从头尾取马
那就转换成了常见的dp的线性模型,类似于矩阵取数的题目了
方程就自然而然的出来了
f[i,j]=max{f[i-1,j]+g[n-(i-j)+1,i],f[i-1,j-1]+g[j,i]}
(小心j=0 和j=i 的情况。。我刚开始就这里挂掉了)
--------------------------------------------
贪心
1.当田忌最慢的马比齐王最慢的马快,赢一场先。因为始终要赢齐王最慢的马,不如用最没用的马来赢它。
 2.当田忌最慢的马比齐王最慢的马慢,和齐王最快的马比,输一场。因为田忌最慢的马始终要输的,不如用它来消耗齐王最有用的马。
 3.当田忌最慢的和齐王最慢的马慢相等时,分4和5讨论。
 4.当田忌最快的马比齐王最快的马快时,赢一场先。因为最快的马的用途就是来赢别人快的马,别人慢的马什么马都能赢。
 5.当田忌最快的马比齐王最快的马慢时,拿最慢的马和齐王最快的马比,输一场,因为反正要输一场,不如拿最没用的马输。
 6.当田忌最快的马和齐王最快的马相等时,这就要展开讨论了,贪心方法是,拿最慢的马来和齐王最快的马比. 

显然是正确的!!!
//--------------------
这里讨论另外一种方程形式:
先排序后肯定从两头取,这个问题上面大神都解释清了(我很弱小解释不能)……
既然从田忌马队两头取,那么可以用一个区间[l,r]表示田忌当前可选的马队,取l或者取r,这样剩下的区间就是[l+1,r]或者[l,r-1]
齐王的马可以利用田忌区间算出来,田忌区间长度是r-l+1,所以赛过n-(r-l+1)场,由此齐王当前的马就是n-(r-l+1)+1=n-r+l
这样方程就有了(递归运行记忆化,别的方法不会……不过应该循环也能写?):
pick(l,r)=max(pick(l+1,r)+g[l,n-r+l],pick(l,r-1)+g[r,n-r+l])
其实原理都差不多来着吧…………只是个人觉得这样好理解些…………
//copy 

//greedy
#include <cstdio>
#include <algorithm>
using namespace std;
const int M=1001;
int qt[2][M];
int n;
int main()
{
    scanf("%d",&n);
    for (int i=1;i>=0;i--)
        for (int j=1;j<=n;j++)
            scanf("%d ",&qt[i][j]);
    int s=0;
    sort(qt[0]+1,qt[0]+n+1,[](int a,int b){ return a<b; });
    sort(qt[1]+1,qt[1]+n+1,[](int a,int b){ return a<b; });
    int i1=1,i2=n,j1=1,j2=n;
    while (i1<=i2)
        if (qt[0][i1]<qt[1][j1]) {i1++;j1++;s++;continue;} else
        if (qt[0][i1]>qt[1][j1]) {s--;j1++;i2--;continue;} else
        if (qt[0][i1]==qt[1][j1])
            if (qt[0][i2]<qt[1][j2]) {i2--;j2--;s++;continue;} else {if (qt[1][j1]<qt[0][i2]) s--;j1++;i2--;continue;}
    printf("%d",s*200);
    return 0;
}

//dp
#include <iostream>
#include <algorithm>
#include <cstring>
#define N 1005
using namespace std;

int n, a[N], b[N];
int f[N][N];
/*
  不妨设齐王的马是从快到慢出场的。 
  f[l][r]: 在田忌的可选区间为[l, r]的时候,能得到的最大奖金,此时已经赛过n - (r - l + 1)场,故此时出场的是齐王的第n - r + l匹马 
  f[l][r] = max (f[l + 1][r] + g (l, n - r + l),  f[l][r - 1] + g (r, n - r + l))
                   田忌选最快的马                   田忌选最慢的马 
*/ 

inline int g (int x, int y)
{
    if (a[x] < b[y])
        return -200;
    if (a[x] > b[y])
        return 200;
    return 0;
}

int work (int l, int r)
{
    if (f[l][r] != -1)
        return f[l][r];
    if (l == r)
        return f[l][r] = g (l, n);
    
    f[l][r] = max (work (l + 1, r) + g (l, n - r + l),  work (l, r - 1) + g (r, n - r + l));
    return f[l][r];
}

bool cmp (int x, int y)
    { return x > y; }

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    for (int i = 1; i <= n; i++)
        cin >> b[i];
        
    sort (a + 1, a + n + 1, cmp);
    sort (b + 1, b + n + 1, cmp);
    memset (f, -1, sizeof (f));
    
    cout << work (1, n) << endl;
    return 0;
}

 

posted @ 2016-09-08 15:58  ACforever  阅读(1322)  评论(0编辑  收藏  举报