田忌赛马(Tian Ji -- The Horse Racing)中的动态规划以及贪心算法

这两天碰到一道看似很简单,但是实际做起来确实比较难的问题,在这里分析讨论一下。

题目:http://acm.hdu.edu.cn/showproblem.php?pid=1052

Tian Ji -- The Horse Racing

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 11410    Accepted Submission(s): 3196

Problem Description
Here is a famous story in Chinese history.

"That was about 2300 years ago. General Tian Ji was a high official in the country Qi. He likes to play horse racing with the king and others."

"Both of Tian and the king have three horses in different classes, namely, regular, plus, and super. The rule is to have three rounds in a match; each of the horses must be used in one round. The winner of a single round takes two hundred silver dollars from the loser."

"Being the most powerful man in the country, the king has so nice horses that in each class his horse is better than Tian's. As a result, each time the king takes six hundred silver dollars from Tian."

"Tian Ji was not happy about that, until he met Sun Bin, one of the most famous generals in Chinese history. Using a little trick due to Sun, Tian Ji brought home two hundred silver dollars and such a grace in the next match."

"It was a rather simple trick. Using his regular class horse race against the super class from the king, they will certainly lose that round. But then his plus beat the king's regular, and his super beat the king's plus. What a simple trick. And how do you think of Tian Ji, the high ranked official in China?"

田忌赛马(Tian Ji -- The Horse Racing)中的动态规划以及贪心算法_第1张图片

Were Tian Ji lives in nowadays, he will certainly laugh at himself. Even more, were he sitting in the ACM contest right now, he may discover that the horse racing problem can be simply viewed as finding the maximum matching in a bipartite graph. Draw Tian's horses on one side, and the king's horses on the other. Whenever one of Tian's horses can beat one from the king, we draw an edge between them, meaning we wish to establish this pair. Then, the problem of winning as many rounds as possible is just to find the maximum matching in this graph. If there are ties, the problem becomes more complicated, he needs to assign weights 0, 1, or -1 to all the possible edges, and find a maximum weighted perfect matching...

However, the horse racing problem is a very special case of bipartite matching. The graph is decided by the speed of the horses --- a vertex of higher speed always beat a vertex of lower speed. In this case, the weighted bipartite matching algorithm is a too advanced tool to deal with the problem.

In this problem, you are asked to write a program to solve this special case of matching problem.

Input
The input consists of up to 50 test cases. Each case starts with a positive integer n (n <= 1000) on the first line, which is the number of horses on each side. The next n integers on the second line are the speeds of Tian’s horses. Then the next n integers on the third line are the speeds of the king’s horses. The input ends with a line that has a single 0 after the last test case.

Output
For each input case, output a line containing a single number, which is the maximum money Tian Ji will get, in silver dollars.

Sample Input
   
   
   
   
3 92 83 71 95 87 74 2 20 20 20 20 2 20 19 22 18 0

Sample Output
   
   
   
   
200 0 0

Source
2004 Asia Regional Shanghai

Recommend
JGShining

动态规划

因为贪心算法很难想,所以从先从dp入手了,首先对田忌还有王的马都按照从快到慢的顺序排列。

定义f[i][j]为,经过了i场比赛后,田忌从最慢的马开始从后往前使用了j匹马的最大收益

一.

f[i-1][j]表示经过了i-1场比赛后,田忌已经使用了j匹慢马的最大收益。当第i场到来时,为了得到f[i][j],田忌只能使用前面的第i-j匹快马,

此时f[i][j]=f[i-1][j]+(田忌使用第i-j匹马与王的第j匹马拼后的收益,使用函数score(i-j,j)表示)

二.

f[i-1][j-1]表示经过了i-1场比赛后,田忌使用了j-1匹慢马的最大收益。当第i场到来时,为了得到f[i][j],田忌使用了第j匹慢马与王的第i匹快马拼,此时f[i][j]=f[i-1][j-1]+(田忌使用第n-j+1匹马与王的第j匹马拼的最大收益score(n-j+1,j),其中n表示所有总共的马数)

所以:

f[i][j]=max(f[i-1][j]+score(i-j,j),f[i-1][j-1]+score(n-j+1,j))


具体程序实现如下:

#include<stdio.h>
#include<algorithm>
using namespace std;

int a[1001],b[1001],f[1001][1001];
/*
 *f[i][j]定义为打了i场比赛,从tanji尾部取出j匹马取得的最大收益
 */
int score(int i, int j){
	if(a[i]>b[j])
	  return 1;
	else if(a[i]<b[j])
	  return -1;
	else return 0;
}
//初始化
void init(int n){
	//将f全部设置为0
	for(int p=0; p<=n; p++)
	  for(int q=0; q<=n; q++)
		f[p][q]=0;
	//设置f[i][0],因为无论tianji还是王都是从最快的马按照递减顺序出场
	//所以每次只需比较当前的两匹马的大小,并在前一次比较的基础上设置当前f值
	for(int i=1; i<=n; i++){
		if(a[i]>b[i])
			f[i][0]=f[i-1][0]+1;
		else if(a[i]<b[i])
		  f[i][0]=f[i-1][0]-1;
		else
		  f[i][0]=f[i-1][0];//这里容易漏掉,相等就使用前一次的最优值
	}
	//设置f[i][i],因为每一次都是tianji都是用最慢的马和王最快的马拼,所以tianji从后往前
	//王从前往后比较
	for(int j=n,g=1; j>=1; j--,g++){
		if(a[j]>b[g])
		  f[g][g]=f[g-1][g-1]+1;
		else if(a[j]<b[g])
		  f[g][g]=f[g-1][g-1]-1;
		else
		  f[g][g]=f[g-1][g-1];
	}
}

int maxnum(int i,int j){
	if(i>j)
	  return i;
	else 
	  return j;
}

bool compare(const int& a, const int& b){
	return a>b;
}

int main(){
	int n,max;
	while(1){
		scanf("%d",&n);
		if(n==0)
		  break;
		for(int i=1; i<=n; i++){
			  scanf("%d",&a[i]);
		}
		for(int x=1; x<=n; x++){
			  scanf("%d",&b[x]);
		}
		sort(a+1,a+n+1,compare);
		sort(b+1,b+n+1,compare);
		init(n);
		//动态规划递推关系
		for(int j=2; j<=n; j++){
		  for(int k=1; k<j; k++){
			f[j][k]=maxnum((f[j-1][k-1]+score(n-k+1,j)),(f[j-1][k]+score(j-k,j)));
		  }
		}
		//得到收益最大并记录
		max = f[n][0];
		for(int g=1; g<=n; g++){
			if(f[n][g]>max)
			  max=f[n][g];
		}
		printf("%d\n",max*200);
	}
}

贪心算法

这个贪心算法确实比较难想,可以参考poj上面的大牛http://poj.org/showmessage?message_id=164719,摘录如下:

贪心的本质在于:田只在有把握赢的情况下拿出快马和王拼,否则用最慢的马比掉王的快马最大程度削弱王的战斗力

*
贪心策略:
1,如果田忌的最快马快于齐王的最快马,则两者比。
(因为若是田忌的别的马很可能就赢不了了,所以两者比)
2,如果田忌的最快马慢于齐王的最快马,则用田忌的最慢马和齐王的最快马比。
(由于所有的马都赢不了齐王的最快马,所以用损失最小的,拿最慢的和他比)
3,若相等,则比较田忌的最慢马和齐王的最慢马
3.1,若田忌最慢马快于齐王最慢马,两者比。
(田忌的最慢马既然能赢一个就赢呗,而且齐王的最慢马肯定也得有个和他比,所以选最小的比他快得。)
3.2,其他,则拿田忌的最慢马和齐王的最快马比。
(反正所有的马都比田忌的最慢马快了,所以这匹马必输,选贡献最大的,干掉齐王的最快马)

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=1005;

int tj[maxn], qw[maxn];

int main()
{
    int n, i, res, max1, max2, min1, min2, cnt;
    while(~scanf("%d", &n) && n)
    {
        for(i=0; i<n; i++)
            scanf("%d", &tj[i]);
        for(i=0; i<n; i++)
            scanf("%d", &qw[i]);
        sort(tj, tj+n);
        sort(qw, qw+n);

        res=0;
        max1=max2=n-1;
        min1=min2=0;
        cnt=0;
        while((cnt++)<n)
        {
            if(tj[max1]>qw[max2])
            {
                res += 200;
                max1--;
                max2--;
            }
            else if(tj[max1]<qw[max2])
            {
                res -= 200;
                min1++;
                max2--;
            }
            else
            {
                if(tj[min1]>qw[min2])
                {
                    res += 200;
                    min1++;
                    min2++;
                }
                else
                {
                    if(tj[min1]<qw[max2]) res -= 200;
                    min1++;
                    max2--;
                }
            }
        }
        printf("%d\n", res);
    }
    return 0;
}


真心佩服古代的孙膑和田忌能想出这么犀利的方法!

参考:
http://poj.org/showmessage?message_id=164719
http://hi.baidu.com/find_chees/item/2d505a8dbb5824814514cf1e

你可能感兴趣的:(田忌赛马(Tian Ji -- The Horse Racing)中的动态规划以及贪心算法)