1. 程式人生 > >LOJ2538. 「PKUWC2018」Slay the Spire【組合數學】

LOJ2538. 「PKUWC2018」Slay the Spire【組合數學】

LINK


思路

首先因為式子後面把方案數乘上了

所以其實只用輸出所有方案的攻擊力總和

然後很顯然可以用強化牌就儘量用

因為每次強化至少把下面的牌翻一倍,肯定是更優的

然後就只有兩種情況

  • 強化牌數量少於k
  • 強化牌數量大於等於k

根據乘法原理,設\(f_{i,j}\)是選i張強化牌用j張的倍數總和,\(g_{i,j}\)是選i張攻擊用j張的倍數總和

\(ans+=f_{k,k}*g_{m-i,m-k}\)

\(ans+=f_{i,k-1}*g_{m-i,1}\)

然後f的計算可以量化大小這個東西,就是先排序

dp出選了i個數,最後一個在j的方案數,這樣前面的j各種不可能選出其他數,對於後面的數直接組合數計算就可以了


#include<bits/stdc++.h>

using namespace std;

const int Mod = 998244353;

const int N = 3e3 + 10;

int n, m, k, a[N], b[N], c[N][N];
int sum[N], f[N][N], g[N][N];

int add(int a, int b) {
  return (a += b) >= Mod ? a - Mod : a;
} 

int mul(int a, int b) {
  return 1ll * a * b % Mod;
} 

void init() {
  for (int i = 0; i < N; i++) c[i][0] = 1;
  for (int i = 1; i < N; i++) {
    for (int j = 1; j <= i; j++) {
      c[i][j] = add(c[i - 1][j], c[i - 1][j - 1]);
    }
  }
}

int calcf(int a, int b) { // 取a張用b張 
  if (a < b) return 0;
  if (!b) return c[n][a]; //**
  int res = 0;
  for (int i = 1; i <= n; i++)
    res = add(res, mul(f[b][i], c[n - i][a - b]));
  return res;
}

int calcg(int a, int b) {
  if (a < b) return 0;
  if (!b) return 0; //**
  int res = 0;
  for (int i = 1; i <= n; i++) 
    res = add(res, mul(g[b][i], c[n - i][a - b]));
  return res;
}

void solve() {
  scanf("%d %d %d", &n, &m, &k);
  for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
  for (int i = 1; i <= n; i++) scanf("%d", &b[i]);
  sort(a + 1, a + n + 1, [&](const int a, const int b) {return a > b;});
  sort(b + 1, b + n + 1, [&](const int a, const int b) {return a > b;});
  for (int i = 1; i <= n; i++) {
    f[1][i] = a[i];
    sum[i] = add(sum[i - 1], a[i]);
  }
  for (int i = 2; i <= n; i++) {
    for (int j = i; j <= n; j++)
      f[i][j] = mul(sum[j - 1], a[j]);
    for (int j = 1; j <= n; j++)
      sum[j] = add(sum[j - 1], f[i][j]);
  }
  for (int i = 1; i <= n; i++) {
    g[1][i] = b[i];
    sum[i] = add(sum[i - 1], b[i]);
  }
  for (int i = 2; i <= n; i++) {
    for (int j = i; j <= n; j++) {
      g[i][j] = add(mul(b[j], c[j - 1][i - 1]), sum[j - 1]);
    }
    for (int j = 1; j <= n; j++)
      sum[j] = add(sum[j - 1], g[i][j]);
  }
  int ans = 0;
  for (int i = max(0, m - n); i <= min(n, m); i++) {
    if (i < k) ans = add(ans, mul(calcf(i, i), calcg(m - i, k - i)));
    else ans = add(ans, mul(calcf(i, k - 1), calcg(m - i, 1)));
  }
  printf("%d\n", ans);
}

int main() {
#ifdef dream_maker
  freopen("input.txt", "r", stdin);
#endif
  init();
  int T; scanf("%d", &T);
  while (T--) solve();
  return 0;
}