免费的糖果

题目描述

Petra和Jan分n个糖果,每个人轮流拿,一次只能拿一个,抽签决定谁先开始拿

每个糖果有两个值x,y, 如果Petra拿了会获得值x, Jan拿了会获得值y。

Petra每次都选择对自己价值最大的(x最大)拿,如果有多个x相同大,选择y值最小的

Jan选择的策略是,要让自己最终获得的总价值最大, 并且在这个的前提下,要让Petra的值也尽量大。

问最终他们获得的价值各是多少?

输入

第一行一个整数:测试用例的个数T(T<=100).

之后每行:

一个整数n(1<=n<=1000) :糖果的个数

一行一个字符串,Petra或者是Jan:谁先选

输出

N行,每行两个数pi和ji(0<=xi,yi<=1000):第i个糖果的x和y每个测试用例输出一行两个数,Petra得到的x价值和Jan得到的y价值。

样例输入

3
4
Petra
100 80
70 80
50 80
30 50
4
Petra
10 1
1 10
6 6
4 4
7
Jan
4 1
3 1
2 1
1 1
1 2
1 3
1 4

样例输出

170 130
14 16
9 1

知识盲区,作为大一菜鸟,目前还不会

借鉴借鉴别人的想法,学习学习

思路
这题的思维很巧妙


先只考虑Petra拿糖的情况,他的策略是贪心的,排序一下,可以知道他一定是从按照顺序依次选择下去的
看样例:
Jan
4 1
3 1
2 1
1 1
1 2
1 3
1 4
这个样例已经按照Petra的贪心策略排序好了,第一个被Jan拿先拿了,第二个一定会被Petra拿去。
接下来,如果Jan选择第3个,那么Petra就会拿第4个,如果Jam选除了第3个以外的任意一个,Petra都会拿走第3个。
所以,Jan每一次的选择策略是,要不要把Petra下一次要拿的那个给“抢过来”!

可以发现假设第一次是Jan开始拿(如果第一次是Petra拿,那么就从第二次开始算)
前1个,Jan最多可以抢1个
前2个,最多可以抢1个(如果拿了第1个,第2个一定会给Petra拿走,如果不拿第1个,那第1个就被Petra拿走了, Jan怎么也不可能拿走2个)
前3个, 最多可以抢2个
前4个,最多可以抢2个
前5个,最多可以抢3个
...(以下省略)
规律是,前i个,最多可以抢(i+1)/2个

所以,我们可以用状态f(i, j),表示前i个,抢j个的时候,自己的最大值

f(i, j) = max{ f(i-1, j), f(i-1,j-1) + y(i) |  当f(i-1, j-1)状态可达时);

另外,题目要有一个限制:在Jan最大价值的情况,让Petra的价值也最大。

那么,sum = x1+x2+x3+...xn, sum是所有糖果对Petra的价值之和

每当Jan抢了一个的时候,Petra的sum就会减少xi, 我们要让所有减少的xi之和最少,
这样,可以把物品x值看作是花费, y值看作是价值,目标是让Jan拿最大价值的情况下,花费最少
那么我们可以再维护一个数组cost(i, j)即可

 1 #include 
 2 #include 
 3 #include 
 4 #include 
 5 #include 
 6 #include 
 7 #include 
 8 using namespace std;
 9  
10 typedef long long int64;
11 const int INF = 0x3f3f3f3f;
12 const double PI  = acos(-1.0);
13  
14 const int MAXN = 1010;
15 struct Node{
16     int x, y;
17     bool operator < (const Node& rhs) const {
18         if (x != rhs.x) return x > rhs.x;
19         return y < rhs.y;
20     }
21 }arr[MAXN];
22  
23 int n;
24 int f[MAXN][MAXN/2];
25 int cost[MAXN][MAXN/2];
26 char name[10];
27  
28 int main(){
29     
30     int nCase;
31     scanf("%d", &nCase);
32     while (nCase--) {
33         scanf("%d", &n);
34  
35         scanf("%s", name);
36         int sum = 0;
37         for (int i = 1; i <= n; ++i) {
38             scanf("%d%d", &arr[i].x, &arr[i].y);
39             sum += arr[i].x;
40         }
41  
42         sort(arr+1, arr+1+n);
43  
44         memset(f, 0, sizeof(f));
45         memset(cost, 0, sizeof(cost));
46  
47         int cur = 0;
48         for (int i = (name[0]=='P'?2:1); i <= n; ++i) {
49             ++cur;
50             for (int j = 1; j <= (cur+1)/2; ++j) {
51                 int& ans = f[i][j] = f[i-1][j];
52                 cost[i][j] = cost[i-1][j];
53  
54                 if (j==1 || f[i-1][j-1]) { 
55                     int tmp = f[i-1][j-1] + arr[i].y;
56                     if (tmp > ans) {
57                         ans = tmp;
58                         cost[i][j] = cost[i-1][j-1] + arr[i].x;
59                     } else if (tmp == ans) {
60                         cost[i][j] = min(cost[i][j], cost[i-1][j-1]+arr[i].x);
61                     }
62                 }
63             }
64         }
65         printf("%d %d
", sum-cost[n][(cur+1)/2], f[n][(cur+1)/2]);
66     }
67     return 0;
68 }

题意:Petra和Jan两个人,有一堆糖果,其中一个人先选,每个糖果有p,j值,代表Petra和Jan取得糖果后的开心值。Petra取糖果的策略是选择P尽量大,J尽量小的糖果,Jan的策略是保证自己最终快乐值尽量大并且Petra的快乐值也尽量大,问最终两个人的快乐值分别是多少。

思路:Petra的策略满足贪心,所以先把糖果按P在按J排序,然后去取,就看Jan会取哪些糖果了,那么每个糖果就可以看成是Jan取不取,但是要注意,由于Petra是一定会往后一个取,所以Jan取糖果的时候是有一定的限制的,该限制为:假如都是Jan先取,1个糖果,Jan可以拿到,2个糖果,Jan可以拿其中一个,3个糖果Jan可以拿其中两个,4个糖果也是其中两个,以此类推,Jan能拿的糖果数为(i + 1)/2。因此dp[i][j]表示Jan在前i个糖果中拿了j个,j <= (i + 1)/2.

状态转移方程为:如果不取 dp[i][j] = dp[i - 1][j] 如果取dp[i][j] = dp[i - 1][j - 1] + c[i].j;由于还要保证Petra尽量大,所以转移过程中记录被Jan取走糖果的p总和作为cost,然后Petra最后的快乐值为sump - cost;所以cost要尽量小,在状态转移过程中维护即可。

题目可能是从Petra先取,不过这个稍微处理一下,假如Petra先取,就把第一个糖果给他,剩下的糖果在去dp。

 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <algorithm>
 4 #define min(a,b) ((a)<(b)?(a):(b))
 5 using namespace std;
 6  
 7 const int N = 1005;
 8 int t, n, dp[N][N], cost[N][N], sum;
 9 char name[10];
10 struct Candy {
11     int p, j;
12 } c[N];
13  
14 bool cmp(Candy a, Candy b) {
15     if (a.p != b.p)
16         return a.p > b.p;
17     return a.j < b.j;
18 }
19  
20 void solve() {
21     int bo = 0;
22     if (name[0] == 'P')
23         bo = 1;
24     memset(dp, 0, sizeof(dp));
25     memset(cost, 0, sizeof(cost));
26     for (int i = 1; i <= n - bo; i++) {
27         for (int j = 1; j <= (i + 1)/2; j++) {
28             if (dp[i - 1][j] > dp[i - 1][j - 1] + c[i + bo].j) {
29                 dp[i][j] = dp[i - 1][j];
30                 cost[i][j] = cost[i - 1][j];
31             }
32             else if (dp[i - 1][j] == dp[i - 1][j - 1] + c[i + bo].j) {
33                 dp[i][j] = dp[i - 1][j];
34                 cost[i][j] = min(cost[i - 1][j], cost[i - 1][j - 1] + c[i + bo].p);
35             }
36             else {
37                 dp[i][j] = dp[i - 1][j - 1] + c[i + bo].j;
38                 cost[i][j] = cost[i - 1][j - 1] + c[i + bo].p;
39             }
40         }
41     }
42     printf("%d %d
", sum - cost[n - bo][(n - bo + 1) / 2], dp[n - bo][(n - bo + 1) / 2]);
43 }
44  
45 int main() {
46     scanf("%d", &t);
47     while (t--) {
48         scanf("%d%s", &n, name);
49         sum = 0;
50         for (int i = 1; i <= n; i++) {
51             scanf("%d%d", &c[i].p, &c[i].j);
52             sum += c[i].p;
53         }
54         sort(c + 1, c + n + 1, cmp);
55         solve();
56     }
57     return 0;
58 }

思路:我们可以知道Petra直接贪心选取即可。所以对糖果的p进行排序,就看Jan会取哪些糖果了,那么每个糖果就可以看成是Jan取不取,但是要注意,由于Petra是一定会取一个剩下的p最大的,所以Jan取糖果的时候是有一定的限制的,限制为:如果Jan先取,1个糖果,Jan可以拿到一个;2个糖果,Jan可以拿其中一个;3个糖果Jan可以拿其中2个;4个糖果也是2个...以此类推,Jan能拿的糖果数为(i + 1)/2。因此dp[i][j]表示Jan在前i个糖果中拿了j个,j <= (i + 1)/2.注意有先后手问题,我们只需考虑Jan先拿,如果Petra先拿就直接从第二个开始变成Jan先手。

转移方程:dp[i][j] = max(dp[i-1][j], dp[i-1][j-1]+a[i].y) 取或不取

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 using namespace std;
 6 typedef long long ll;
 7 const int maxn = 1e3+5;
 8 char str[maxn];
 9 struct node
10 {
11     int x, y;
12     bool operator < (const node &a) const
13     {
14         if(x == a.x)
15             return y < a.y;
16         else
17             return x > a.x;
18     }
19 }a[maxn];
20 int dp[maxn][maxn], cost[maxn][maxn];
21  
22 int main(void)
23 {
24     int t, n;
25     cin >> t;
26     while(t--)
27     {
28         scanf("%d", &n);
29         scanf(" %s", str);
30         int s = 1, sum = 0;
31         if(str[0] == 'P') s = 2;
32         for(int i = 1; i <= n; i++)
33             scanf("%d%d", &a[i].x, &a[i].y), sum += a[i].x;
34         sort(a+1, a+1+n);
35         int cur = 0;
36         memset(dp, 0, sizeof(dp));
37         for(int i = s; i <= n; i++)
38         {
39             cur++;
40             for(int j = 1; j <= (cur+1)/2; j++)
41             {
42                 dp[i][j] = dp[i-1][j];
43                 cost[i][j] = cost[i-1][j];
44                 if(dp[i-1][j-1]+a[i].y > dp[i][j])
45                 {
46                     dp[i][j] = dp[i-1][j-1]+a[i].y;
47                     cost[i][j] = cost[i-1][j-1]+a[i].x;
48                 }
49                 else if(dp[i-1][j-1]+a[i].y == dp[i][j])
50                     cost[i][j] = min(cost[i][j], cost[i-1][j-1]+a[i].x);
51             }
52         }
53         printf("%d %d
", sum-cost[n][(cur+1)/2], dp[n][(cur+1)/2]);
54     }
55     return 0;
56 }
原文地址:https://www.cnblogs.com/jiamian/p/10646339.html