免费的糖果
题目描述
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\n", 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\n", 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\n", sum-cost[n][(cur+1)/2], dp[n][(cur+1)/2]); 54 } 55 return 0; 56 }