「PKUWC2018」Slay the Spire - 贪心 + 动态规划

2n2n 张牌,每张牌上都写着一个数字wiw_i,一共有两种类型的牌,每种类型各 nn 张:

  1. 攻击牌:打出后对对方造成等于牌上的数字的伤害。
  2. 强化牌:打出后,假设该强化牌上的数字为 xx,则其他剩下的攻击牌的数字都会乘上 xx保证强化牌上的数字都大于 1

现在等概率随机从卡组中抽出 mm 张牌,最多打出 kk 张牌,求在最优出牌策略下期望造成的伤害×(2nm) mod998244353\times \binom{2n}{m}~\mathrm{mod}998244353的值

n3000n\le 3000

LOJ2538

Solution

发现这道题显然和期望并没有什么关系,就是求所有情况的伤害之和

先考虑最优策略是什么

贪心考虑不难发现,要尽量多打强化牌,且强化牌足够多的话只打出一张攻击牌(强化牌上的数字大于1且是整数,所以至少是两倍)。具体来说,如果抽的mm张牌中有aa张强化牌,那么

  1. a<ka<k:打出aa张强化牌和前kak-a大的攻击牌
  2. aka\ge k:打出k1k-1大的强化牌和最大的一张攻击牌

考虑dp,设表示选了张强化牌,打出张的倍率和,表示选了张攻击牌,打出张的原始伤害和,那么上面两种情况就分别对应

注意到打出的牌都是最大的某几张,那么是个套路,可以把原数组排序后再dp

f[i][j]f[i][j]表示在前ii张强化牌中,总共打出jj张强化牌,第ii张必选的倍率和,g[i][j]g[i][j]类似,那么有

接下来考虑计算

f[i][j]=wip=1i1f[p][j1]g[i][j]=wi(i1j1)+p=1i1g[p][j1]\begin{aligned} f[i][j] &= w_i * \sum_{p=1}^{i-1}f[p][j-1]\\ g[i][j] &= w_i * \binom{i-1}{j-1}+\sum_{p=1}^{i-1}g[p][j-1] \end{aligned}

下面的式子乘的组合数是前面的方案数的总和,因为每种方案都会加上wiw_i。还需要注意g[0][0]=1g[0][0]=1

Summary

怎么说呢,dp题一直都是自己不擅长的,还是因为题目做少了。

比如像这道题,看上去很复杂,但实际上思维难度并不高。每一步思路都很清晰,只需要一步一步转化问题,按着思路往下推就能做出来了

可是自己做题时往往不敢去设dp方程,哪怕设出来了也只是想个大概,不敢继续往下推,觉得自己是错的;也不喜欢把已经推出来了的东西或者dp式子写下来,仔细分析。这就导致了做题的时候粗略想两下感觉做不出就去看题解

这个习惯一定得改,对待每道题都要当是考试,把自己能想到的做法都要去尝试,静下心推式子

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#include <bits/stdc++.h>

#define x first
#define y second
#define y1 Y1
#define y2 Y2
#define mp make_pair
#define pb push_back

using namespace std;

typedef long long LL;
typedef pair<int, int> pii;

template <typename T> inline int Chkmax (T &a, T b) { return a < b ? a = b, 1 : 0; }
template <typename T> inline int Chkmin (T &a, T b) { return a > b ? a = b, 1 : 0; }

inline void proc_status()
{
ifstream t ("/proc/self/status");
cerr << string (istreambuf_iterator <char> (t), istreambuf_iterator <char> ()) <<endl;
}

template <typename T> T read ()
{
T sum = 0, fl = 1; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') fl = -1;
for (; isdigit(ch); ch = getchar()) sum = (sum << 3) + (sum << 1) + ch - '0';
return sum * fl;
}

const int Maxn = 3e3 + 100, Mod = 998244353;

int N, M, K, A[Maxn], B[Maxn];
int fac[Maxn], ifac[Maxn];

inline int Pow (int a, int b)
{
int ans = 1;
while (b) { if (b & 1) ans = 1ll * ans * a % Mod; a = 1ll * a * a % Mod; b >>= 1; }
return ans;
}

inline int C (int n, int m) { return 1ll * fac[n] * ifac[m] % Mod * ifac[n - m] % Mod; }

int f[Maxn][Maxn], g[Maxn][Maxn], Sum[Maxn];

inline void Add (int &a, int b) { a += b; if (a >= Mod) a -= Mod; }

inline int F (int x, int y)
{
int ans = 0;
for (int i = 0; i <= N; ++i) Add (ans, 1ll * f[i][y] * C(N - i, x - y) % Mod);
return ans;
}

inline int G (int x, int y)
{
int ans = 0;
for (int i = 1; i <= N; ++i) Add (ans, 1ll * g[i][y] * C(N - i, x - y) % Mod);
return ans;
}

inline void Solve ()
{
sort(A + 1, A + N + 1, greater <int> ());
sort(B + 1, B + N + 1, greater <int> ());
memset(Sum, 0, sizeof Sum);
f[0][0] = 1;
for (int i = 1; i <= N; ++i)
{
for (int j = 1; j <= i; ++j) Add (Sum[j], f[i - 1][j]);
f[i][1] = A[i];
for (int j = 2; j <= i; ++j) f[i][j] = 1ll * A[i] * Sum[j - 1] % Mod;
}

memset(Sum, 0, sizeof Sum);
for (int i = 1; i <= N; ++i)
{
for (int j = 1; j <= i; ++j) Add (Sum[j], g[i - 1][j]);
g[i][1] = B[i];
for (int j = 2; j <= i; ++j) g[i][j] = (1ll * B[i] * C(i - 1, j - 1) % Mod + Sum[j - 1]) % Mod;
}

int ans = 0;
for (int a = 0; a <= min(N, M); ++a)
{
if (a < K) Add (ans, 1ll * F(a, a) * G(M - a, K - a) % Mod);
else Add (ans, 1ll * F(a, K - 1) * G(M - a, 1) % Mod);
}
cout<<ans<<endl;
}

inline void Input ()
{
N = read<int>(), M = read<int>(), K = read<int>();
for (int i = 1; i <= N; ++i) A[i] = read<int>();
for (int i = 1; i <= N; ++i) B[i] = read<int>();
}

inline void Init (int maxn)
{
fac[0] = 1;
for (int i = 1; i <= maxn; ++i) fac[i] = 1ll * fac[i - 1] * i % Mod;
ifac[maxn] = Pow(fac[maxn], Mod - 2);
for (int i = maxn - 1; i >= 0; --i) ifac[i] = 1ll * ifac[i + 1] * (i + 1) % Mod;
}

int main()
{
#ifndef ONLINE_JUDGE
freopen("A.in", "r", stdin);
freopen("A.out", "w", stdout);
#endif
Init(3000);
int Testcase = read<int>();
while (Testcase--)
{
Input();
Solve();
}
return 0;
}
感谢你的赞赏!