P10802 [CEOI2024] 核酸檢測 題解

下蛋爷發表於2024-09-21

Description

link

Solution

Sub1 可以直接對於每個每個人問一次,考慮 Sub2 怎麼做。

首先有個顯然的想法是二分出第一個陽性的人,次數大概是 \(NP\log N\),分數不高。

注意到每個位置的狀態是隨機生成的,並且題目要求的次數非常優,所以可以考慮利用期望 dp 求出最小期望操作次數。

\(f_{i,j}\) 表示目前剩下 \(i\) 個人沒有判斷,且確定了有 \(j\) 個人,滿足這 \(j\) 個人至少有一個陽性的最小期望操作次數。

對於 \(j=1\) 的情況,把那個已經確定陽了的人去掉,轉移即為 \(f_{i,1}\leftarrow f_{i-1,0}\)

對於 \(j>1\) 的情況,考慮列舉 \(j\) 個人中的某 \(k\) 個,如果這 \(k\) 個人有陽的,就轉移到 \(f_{i,k}\),這一部分的機率為 \(\frac{(1-P)^k-(1-P)^j}{1-(1-P)^j}\)。否則把這 \(k\) 個人去掉,即轉移到 \(f_{i-k,j-k}\)。所以:

\[f_{i,j}=\min_{k}{\left\{\frac{(1-P)^k-(1-P)^j}{1-(1-P)^j}\cdot f_{i,k}+\left(1-\frac{(1-P)^k-(1-P)^j}{1-(1-P)^j}\right)\cdot f_{i-k,j-k}\right\}} \]

對於 \(j=0\) 的情況,列舉 \(i\) 個人中的某 \(k\) 個,如果這 \(k\) 個人沒有陽的,就轉移到 \(f_{i-k,j-k}\),這一部分的機率為 \((1-P)^k\)。否則把這 \(k\) 個人去掉,轉移到 \(f_{i,k}\)。所以:

\[f_{i,0}=\min_{k}{\left\{\left(1-P\right)^kf_{i-k,j-k}+\left(1-\left(1-P\right)^k\right)f_{i,k}\right\}} \]

經過測試,當 \(P=0.2\) 時,\(f_{1000,0}=727.957\),可以透過。

輸出方案可以透過 dp 過程中的最優轉移點來做。

時間複雜度:\(O(n^3)\)

Code

#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 1e3 + 5;

int n;
int trans[kMaxN][kMaxN];
double p, pw[kMaxN], f[kMaxN][kMaxN];
std::mt19937 rnd(std::random_device{}());

bool test_students(std::vector<bool> mask) {
  assert(mask.size() == (size_t)n);

  std::string mask_str(n, ' ');
  for (int i = 0; i < n; i++) mask_str[i] = mask[i] ? '1' : '0';

  printf("Q %s\n", mask_str.c_str());
  fflush(stdout);

  char answer;
  scanf(" %c", &answer);
  return answer == 'P';
}

bool ask(std::vector<int> vec) {
  std::vector<bool> mask(n);
  for (auto x : vec) mask[x] = 1;
  return test_students(mask);
}

void del(std::vector<int> &v1, std::vector<int> &v2) {
  static bool vis[kMaxN] = {0};
  for (auto x : v1) vis[x] = 1;
  for (auto x : v2) vis[x] = 0;
  std::vector<int> tmp;
  std::swap(v1, tmp);
  for (auto x : tmp) {
    if (vis[x]) v1.emplace_back(x);
    vis[x] = 0;
  }
}

void solve(std::vector<int> v1, std::vector<int> v2, std::vector<bool> &answer) {
  static bool vis[kMaxN] = {0};
  int a = (int)v1.size(), b = (int)v2.size();
  if (!a) return;
  if (b == 1) {
    answer[v2[0]] = 1;
    del(v1, v2);
    solve(v1, {}, answer);
  } else if (!b) {
    int k = trans[a][b];
    std::vector<int> vec;
    for (int i = 0; i < k; ++i) vec.emplace_back(v1[i]);
    if (!ask(vec)) {
      del(v1, vec);
      solve(v1, {}, answer);
    } else {
      solve(v1, vec, answer);
    }
  } else {
    int k = trans[a][b];
    std::vector<int> vec;
    for (int i = 0; i < k; ++i) vec.emplace_back(v2[i]);
    if (!ask(vec)) {
      del(v1, vec), del(v2, vec);
      solve(v1, v2, answer);
    } else {
      solve(v1, vec, answer);
    }
  }
}

std::vector<bool> find_positive(bool op) {
  if (!op) {
    std::vector<bool> answer(n);
    for (int i = 0; i < n; ++i) {
      std::vector<bool> vec(n);
      vec[i] = 1;
      answer[i] = test_students(vec);
    }
    return answer;
  } else {
    std::vector<int> vec;
    std::vector<bool> answer(n);
    for (int i = 0; i < n; ++i) vec.emplace_back(i);
    solve(vec, {}, answer);
    return answer;
  }
}

void prework() {
  pw[0] = 1;
  for (int i = 1; i <= n; ++i) pw[i] = pw[i - 1] * (1 - p);
  for (int i = 1; i <= n; ++i) {
    f[i][0] = 1e18, f[i][1] = f[i - 1][0];
    for (int j = 2; j <= i; ++j) {
      f[i][j] = 1e18;
      for (int k = 1; k <= j; ++k) {
        double pr = (pw[k] - pw[j]) / (1 - pw[j]); // 選的 k 個沒有的機率
        double val = pr * f[i - k][j - k] + (1 - pr) * f[i][k] + 1;
        if (val < f[i][j]) {
          f[i][j] = val, trans[i][j] = k;
        }
      }
    }
    for (int j = 1; j <= i; ++j) {
      double pr = pw[j];
      double val = pr * f[i - j][0] + (1 - pr) * f[i][j] + 1;
      if (val < f[i][0]) {
        f[i][0] = val, trans[i][0] = j;
      }
    }
  }
}

int32_t main() {
  int T;
  scanf("%d %lf %d", &n, &p, &T);
  if (T > 1) prework();
  for (int i = 0; i < T; i++) {
    std::vector<bool> answer = find_positive(T > 1);
    assert(answer.size() == (size_t)n);

    std::string answer_str(n, ' ');
    for (int j = 0; j < n; j++) answer_str[j] = answer[j] ? '1' : '0';

    printf("A %s\n", answer_str.c_str());
    fflush(stdout);

    char verdict;
    scanf(" %c", &verdict);
    if (verdict == 'W') exit(0);
  }

  return 0;
}

相關文章