P10802 [CEOI2024] 核酸检测 题解

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;
}
posted @ 2024-09-21 20:25  下蛋爷  阅读(41)  评论(0)    收藏  举报