P10802 [CEOI2024] 核酸检测 题解

Description

link

Solution

Sub1 可以直接对于每个每个人问一次,考虑 Sub2 怎么做。

首先有个显然的想法是二分出第一个阳性的人,次数大概是 NPlogNNP\log N,分数不高。

注意到每个位置的状态是随机生成的,并且题目要求的次数非常优,所以可以考虑利用期望 dp 求出最小期望操作次数。

fi,jf_{i,j} 表示目前剩下 ii 个人没有判断,且确定了有 jj 个人,满足这 jj 个人至少有一个阳性的最小期望操作次数。

对于 j=1j=1 的情况,把那个已经确定阳了的人去掉,转移即为 fi,1fi1,0f_{i,1}\leftarrow f_{i-1,0}

对于 j>1j>1 的情况,考虑枚举 jj 个人中的某 kk 个,如果这 kk 个人有阳的,就转移到 fi,kf_{i,k},这一部分的概率为 (1P)k(1P)j1(1P)j\frac{(1-P)^k-(1-P)^j}{1-(1-P)^j}。否则把这 kk 个人去掉,即转移到 fik,jkf_{i-k,j-k}。所以:

fi,j=mink{(1P)k(1P)j1(1P)jfi,k+(1(1P)k(1P)j1(1P)j)fik,jk}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=0j=0 的情况,枚举 ii 个人中的某 kk 个,如果这 kk 个人没有阳的,就转移到 fik,jkf_{i-k,j-k},这一部分的概率为 (1P)k(1-P)^k。否则把这 kk 个人去掉,转移到 fi,kf_{i,k}。所以:

fi,0=mink{(1P)kfik,jk+(1(1P)k)fi,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.2P=0.2 时,f1000,0=727.957f_{1000,0}=727.957,可以通过。

输出方案可以通过 dp 过程中的最优转移点来做。

时间复杂度:O(n3)O(n^3)

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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#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;
}

P10802 [CEOI2024] 核酸检测 题解
https://sobaliuziao.github.io/2024/09/21/post/7fcf1a88.html
作者
Egg_laying_master
发布于
2024年9月21日
许可协议