题解 CF321B 【Ciel and Duel】

Rbu_nas

2019-08-05 21:25:33

Solution

[题目大意](https://www.luogu.org/problem/CF321B) 这道题目难处理的是我方的牌可以打完,也可以不打完,并且由样例二可以发现并不是出数值相近的牌就能赢,很像是 dp (事实上也是) 那到底如何出牌才能收益最大呢?考虑全两种情况:打完自己的牌、不打完自己的牌。 --- _策略一:打爆对方所有牌_ 那么很明显此时先攻击 DEF 更优。因为我们期望的是打完所有牌,如果自己还有剩余直接构成真伤。 如果先攻击 ATK 很可能出现打不动 DEF 的尴尬局面。所以我们每次找与对方 DEF 最接近的牌打出,然后再找和对方 ATK 最相近的牌打。最后,如果还有剩余直接累加答案就好了。 为什么找和对方 ATK 最接近的牌打呢?貌似拿自己最大的打别人最小的更划算啊 其实,这就是我们的另一种策略了。因为不打完自己的所有牌,那就必须让打出的牌造成的伤害最大嘛 _策略二:中途退出_ 这种策略下我们不打 DEF,因为要把牌都留着打 ATK。而且如果想着造成 ATK 伤害最大下同时试着打完 DEF 再造成真伤,就是策略一策略二两头都没顾着了,很容易被 hack --- 通过简述两种情况下的策略,我们很容易发现它们都能被 hack。 犹豫不定,想打完所有牌可见样例二 每次选最大打最小,容易构造: ``` 3 4 ATK 1 ATK 2 ATK 3 4 3 2 2 ``` 所以,我们要考虑全两种情况,把两种贪心策略中择优啊 当然,dp 和 最小费用最大流都可做,可以参考 CF 的官方题解。 本题还有的坑点是力量值是非负的,初值要设为 `-1` code : ```cpp #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; typedef long long LL; const int N = 100003; int n, m; int totAtk, totDef; int xAtk[N]; int yAtk[N], yDef[N]; //totAtk totDef为对方手中ATK与DEF的数目,便于排序 //xAtk[]是我方卡牌数组 //yAtk[] yDef[]是对方卡牌数组 int copy_xAtk[N]; int copy_yAtk[N], copy_yDef[N]; //这里有上述三个数组的备份 //为了防止做完一种策略对后面策略中数组的影响 //rush便是打完所有牌的策略 inline LL rush(int *xAtk, int *yAtk, int *yDef) { LL ret = 0; sort(xAtk + 1, xAtk + m + 1); sort(yDef + 1, yDef + totDef + 1); sort(yAtk + 1, yAtk + totAtk + 1); //先进行排序,然后找出与DEF相近的牌打 for(register int i = 1; i <= totDef; ++i) { int P = upper_bound(xAtk + 1, xAtk + m + 1, yDef[i]) - xAtk; if(xAtk[P] < yDef[i]) return 0; //如果连DEF都还不能打完,那么这种策略收益为0 xAtk[P] = -1; yDef[i] = -1; } sort(xAtk + 1, xAtk + m + 1); //注意这里要重新排序!数组的值发生了变化 for(register int i = 1; i <= totAtk; ++i) { int P = lower_bound(xAtk + 1, xAtk + m + 1, yAtk[i]) - xAtk; if(xAtk[P] < yAtk[i] || xAtk[P] == -1 || yAtk[i] == -1) return ret; //如果打不动ATK了,只能删库带着收益跑路了 ret += xAtk[P] - yAtk[i]; xAtk[P] = -1; yAtk[i] = -1; } for(register int i = 1; i <= m; ++i) if(xAtk[i] != -1) ret += xAtk[i]; //康康还有没有真伤qwq return ret; } //策略二,把大牌直接炸出去 inline LL blow(int *xAtk, int *yAtk, int *yDef) { LL ret = 0; sort(xAtk + 1, xAtk + m + 1); sort(yAtk + 1, yAtk + totAtk + 1); //这里的变量条件的意思是 //如果我的牌还没打完,就继续循环 //因为可能还有DEF,所以这里不累计真伤 //对方牌值-1就是没ATK牌了 for(register int i = m, j = 1; i >= 1; --i, ++j) if(xAtk[i] >= yAtk[j]) { if(yAtk[j] != -1) { ret += xAtk[i] - yAtk[j]; xAtk[i] = -1; yAtk[j] = -1; } } return ret; } signed main() { scanf("%d%d", &n, &m); char str[7]; //设初值 memset(xAtk, -1, sizeof(xAtk)); memset(yAtk, -1, sizeof(yAtk)); memset(yDef, -1, sizeof(yDef)); memset(copy_xAtk, -1, sizeof(copy_xAtk)); memset(copy_yAtk, -1, sizeof(copy_yAtk)); memset(copy_yDef, -1, sizeof(copy_yDef)); for(register int i = 1; i <= n; ++i) { scanf("%s", str); if(str[0] == 'A') { ++totAtk; scanf("%d", &yAtk[totAtk]); } else { ++totDef; scanf("%d", &yDef[totDef]); } } for(register int i = 1; i <= m; ++i) scanf("%d", &xAtk[i]), copy_xAtk[i] = xAtk[i]; for(register int i = 1; i <= totAtk; ++i) copy_yAtk[i] = yAtk[i]; for(register int i = 1; i <= totDef; ++i) copy_yDef[i] = yDef[i]; printf("%lld", max( rush(xAtk, yAtk, yDef), blow(copy_xAtk, copy_yAtk, copy_yDef) )); //两种策略择优输出。 return 0; } ```