第46届ICPC亚洲区域赛(昆明)题解
B. Blocks
题意
给定\(n\)个可涂色矩形范围\(((x_1,y_1),(x_2,y_2))\),每一步将会在\(n\)个可涂色矩形范围内均匀随机独立选择一个进行涂色,问将矩形\(((0,0),(W,H))\)涂黑的期望最少步数。\((n\leq 10)\)
分析
由于\(n\)很小,所以将\(n\)个可涂色矩形范围离散化后,可以随便用一种办法,预处理出涂色的\(2^n\)种状态是否已经涂满。
记已经涂满\(((0,0),(W,H))\)的状态为集合\(S\)
考虑期望dp,定义\(f(T)\)为已经将\(T\)集合里的矩形涂完后的期望,根据全期望公式,有
由于\(f(T\cup i)\)可能就是\(f(T)\),故将\(f(T)\)移到一边,解得
很容易使用状压dp实现
代码
#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <iostream>
#include <vector>
using namespace std;
typedef long long Lint;
const Lint mod = 998244353;
const int maxn = 20;
int n, W, H;
struct Rect {
int x1, y1, x2, y2;
} a[maxn];
vector<int> x, y;
int vis[1 << 10][25][25];
Lint dp[1 << 10];
inline Lint fpow(Lint a, Lint b, Lint mod) {
Lint res = 1;
for (; b; b >>= 1) {
if (b & 1)
res = res * a % mod;
a = a * a % mod;
}
return res;
}
inline Lint inv(Lint x) {
return fpow(x, mod - 2, mod);
}
inline Lint add(Lint a, Lint b) {
Lint res = a + b;
if (res >= mod)
return res - mod;
else
return res;
}
int log2(int t) {
int res = 0;
for (; t > 1; t >>= 1)
res++;
return res;
}
void solve() {
cin >> n;
cin >> W >> H;
for (int i = 0; i < (1 << n); i++)
dp[i] = 0;
x.clear(), y.clear();
x.push_back(0), x.push_back(W);
x.push_back(0), x.push_back(H);
for (int i = 1; i <= n; i++) {
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
x1 = min(W, x1), y1 = min(H, y1);
x2 = min(W, x2), y2 = min(H, y2);
a[i] = {x1, y1, x2, y2};
x.push_back(x1);
x.push_back(x2);
y.push_back(y1);
y.push_back(y2);
}
sort(x.begin(), x.end());
x.erase(unique(x.begin(), x.end()), x.end());
sort(y.begin(), y.end());
y.erase(unique(y.begin(), y.end()), y.end());
for (int i = 1; i <= n; i++) {
a[i].x1 = lower_bound(x.begin(), x.end(), a[i].x1) - x.begin() + 1;
a[i].x2 = lower_bound(x.begin(), x.end(), a[i].x2) - x.begin();
a[i].y1 = lower_bound(y.begin(), y.end(), a[i].y1) - y.begin() + 1;
a[i].y2 = lower_bound(y.begin(), y.end(), a[i].y2) - y.begin();
}
W = x.size() - 1, H = y.size() - 1;
for (int i = 0; i < (1 << n); i++) {
for (int j = 0; j <= W; j++) {
for (int k = 0; k <= H; k++) {
vis[i][j][k] = 0;
}
}
}
for (int i = 1; i < (1 << n); i++) {
int j = log2(i);
int k = i & ~(1 << j);
if (vis[k][0][0]) {
vis[i][0][0] = 1;
continue;
}
for (int x = 1; x <= W; x++) {
for (int y = 1; y <= H; y++) {
vis[i][x][y] = vis[k][x][y];
}
}
for (int x = a[j + 1].x1; x <= a[j + 1].x2; x++) {
for (int y = a[j + 1].y1; y <= a[j + 1].y2; y++) {
vis[i][x][y] = 1;
}
}
for (int x = 1; x <= W; x++) {
for (int y = 1; y <= H; y++) {
if (!vis[i][x][y]) {
goto g1;
}
}
}
vis[i][0][0] = 1;
g1:;
}
if (!vis[(1 << n) - 1][0][0]) {
cout << "-1\n";
return;
}
for (int i = (1 << n) - 2; i >= 0; i--) {
if (vis[i][0][0])
continue;
int cnt = 0;
for (int j = 0; j < n; j++) {
if ((i >> j) & 1) {
continue;
}
cnt++;
dp[i] = add(dp[i], dp[i | (1 << j)]);
}
dp[i] = add(dp[i], n);
dp[i] = dp[i] * inv(cnt) % mod;
}
cout << dp[0] << '\n';
}
int main() {
int T;
cin >> T;
while (T--)
solve();
}
C. Cup of Water
题意
每次会在\([0,a]\)之间随机选一个数\(t\),往水桶里装\(t\)升水,问使得水桶里的水大于等于\(1\)升的期望最少次数。其中\(0.05\leq a\leq 10^9\)。
方法一
利用多重积分可以证明,\(n\)维下,有\(n\)条长度为\(x\)的边且它们互相垂直的物体(下称单纯形)的\(n\)维测度为
对于区域\(x_1+x_2+\dots+x_n\leq1\)与\(0\leq x_i\leq a\)的交集的测度可以使用容斥原理计算。
在\(x_1,x_2,\dots,x_n\geq 0\)的条件下,
任选\(0\)个变量,令其大于\(a\),其余无额外约束,表示至少有\(0\)个变量大于\(a\),共有\(\binom{n}{0}\)种选法,所以要加上\(\binom{n}{1}\)个长度为\(1\)的单纯形的测度
任选\(1\)个变量,令其大于\(a\),其余无额外约束,表示至少有\(1\)个变量大于\(a\),共有\(\binom{n}{1}\)种选法,所以要减去\(\binom{n}{1}\)个长度为\(1-a\)的单纯形的测度
任选\(2\)个变量,令其大于\(a\),其余无额外约束,表示至少有\(2\)个变量大于\(a\),共有\(\binom{n}{2}\)种选法,所以要加上\(\binom{n}{2}\)个长度为\(1-2a\)的单纯形的测度
\(\dots\)
直到任选\(i\)个变量时,\(1-ia<0\),不存在有\(i\)个变量同时大于\(a\)的可能,计算结束,故式子如下
计随机变量\(X\)为每次均匀随机走\([0,a]\),达到大于等于\(1\)所需的最少次数,则有
由此可得
故期望为
方法二
设\(f(x)\)为每次均匀随机走\([0,a]\),达到大于等于\(x\)的期望次数,由全期望公式
两侧对\(x\)求导
对于这个式子,有两种处理办法,一种是利用导数的定义进行近似计算,另一种是像方法一那样,通过这个式子,推出最终结果的式子。
法一
进行近似计算,当\(\Delta x\to 0\)时,有
令初值\(f(0)=1\),\(f(1)\)即为答案
由于题目是多组输入,且步长\([0,a]\),大于等于\(1\)的期望步数,等价于,步长\([0,1]\),大于等于\(\frac{1}{a}\)的期望步数,故而把递推式变成
令初值\(f(0)=1\),\(f(\frac{1}{a})\)即为答案,这样在多组输入之前就可以进行预处理。
法二
像法一一样先转化为步长为\([0,1]\)的问题,递推式变成
对\(x\)范围进行分类讨论
-
当\(0<x\leq 1\)时,\(f(x-1)=0\),
解得\(f(x)=C\mathrm{e}^x\),
当\(x\to 0^+\)时,\(f(x)\to1\),故\(C=1\),
\(f(x)=\mathrm{e}^x\)
-
当\(1<x\leq 2\)时,\(f(x-1)=\mathrm{e}^{x-1}\),
解得\(f(x)=C\mathrm{e}^x-x\mathrm{e}^{x-1}\),
由连续性,当\(x\to 1^+\)时,\(f(x)\to f(1)=\mathrm{e}\),故\(C=1+\frac{1}{\mathrm{e}}\),
\(f(x)=\mathrm{e}^x-(x-1)\mathrm{e}^{x-1}\)
-
当\(2<x\leq 3\)时,\(f(x-1)=\mathrm{e}^{x-1}-(x-2)\mathrm{e}^{x-2}\)
解得\(f(x)=C\mathrm{e}^x-x\mathrm{e}^{x-1}+\frac{1}{2}(x-2)^2\mathrm{e}^{x-2}\),
由连续性,当\(x\to 2^+\)时,\(f(x)\to f(2)=\mathrm{e}^2-\mathrm{e}\),故\(C=1+\frac{1}{\mathrm{e}}\),
\(f(x)=\mathrm{e}^x-(x-1)\mathrm{e}^{x-1}+\frac{1}{2}(x-2)^2\mathrm{e}^{x-2}\)
\(\dots\)
找规律,并数归可得,当\(k<x\leq(k+1)\)时(\(k\)为非负整数)
令\(x=\frac{1}{a}\),得
代码
代式子
#include <cmath>
#include <cstdio>
void solve() {
double x;
scanf("%lf", &x);
x = 1 / x;
double ans = 0, fac = 1;
for (int i = 0; i < x; i++) {
if (i & 1)
ans -= fac * pow(x - i, i) * exp(x - i);
else
ans += fac * pow(x - i, i) * exp(x - i);
fac /= (i + 1);
}
printf("%.10lf\n", ans);
}
int main() {
int T;
scanf("%d", &T);
while (T--) solve();
return 0;
}
近似计算
#include <algorithm>
#include <cmath>
#include <cstdio>
using namespace std;
const int maxn = 1e5;
const double dx = 1.0 / maxn;
double f[maxn * 20 + 100];
void solve() {
double x;
scanf("%lf", &x);
int t = 1 / x * maxn;
printf("%.10lf\n", f[t]);
}
int main() {
f[0] = 1;
for (int i = 0; i < 20 * maxn + 99; i++)
f[i + 1] = f[i] + dx * (f[i] - (i - maxn >= 0 ? f[i - maxn] : 0));
int T;
scanf("%d", &T);
while (T--)
solve();
return 0;
}
F. Find the Maximum
题意
给一个\(n\)个点的树,点\(i\)的权值为\(a_i\),定义简单路径的长度为经过点的个数。找一条长度大于\(1\)的简单路径(记为集合\(V\)),使得\(\frac{\sum_{u \in V}(-x^2+a_u x)}{|V|}\)最大,输出最大值。
分析
将目标式子化简得到
它是二次函数,最大值为
故只需要让\(\frac{\sum_{u \in v}a_u}{|V|}\)最小或者最大即可。
注意到这个式子是路径权值的均值。
显然任何\(1\)条长度大于或等于\(4\)的路径都能拆成\(2\)条长度大于\(1\)的路径,而这\(2\)条长度大于\(1\)的路径中必然满足其中\(1\)条的平均值不大于拆分前的平均值,另一条不小于拆分前的平均值,故只需要找所有长度为\(2\)或\(3\)的路径,找到均值的最小值和最大值,代入上面的式子,比大小,输出最大的即可。
代码
#include <iomanip>
#include <iostream>
#include <vector>
using namespace std;
const int maxn = 1e5 + 10;
const int MIN_INT = (int)0x80000000;
const int MAX_INT = (int)0x7fffffff;
vector<int> G[maxn];
int n;
int fa[maxn], b[maxn], dp[2][maxn];
void dfs(int u, int f) {
fa[u] = f;
int ma[2] = {MIN_INT, MIN_INT};
int mi[2] = {MAX_INT, MAX_INT};
for (auto v : G[u]) {
if (v == f)
continue;
if (b[v] >= ma[0]) {
ma[1] = ma[0];
ma[0] = b[v];
} else if (b[v] >= ma[1]) {
ma[1] = b[v];
}
if (b[v] <= mi[0]) {
mi[1] = mi[0];
mi[0] = b[v];
} else if (b[v] <= mi[1]) {
mi[1] = b[v];
}
dfs(v, u);
}
if (ma[0] == MIN_INT || ma[1] == MIN_INT) {
dp[0][u] = MIN_INT;
} else {
dp[0][u] = ma[0] + ma[1] + b[u];
}
if (mi[0] == MAX_INT || mi[1] == MAX_INT) {
dp[1][u] = MAX_INT;
} else {
dp[1][u] = mi[0] + mi[1] + b[u];
}
}
double get_max() {
double res = -1e12;
for (int i = 1; i <= n; i++) {
if (fa[i]) {
res = max(res, 1.0 * (b[i] + b[fa[i]]) / 2);
res = max(res, -1.0 * (b[i] + b[fa[i]]) / 2);
}
if (fa[i] && fa[fa[i]]) {
res = max(res, 1.0 * (b[i] + b[fa[i]] + b[fa[fa[i]]]) / 3);
res = max(res, -1.0 * (b[i] + b[fa[i]] + b[fa[fa[i]]]) / 3);
}
if (dp[0][i] != MIN_INT) {
res = max(res, 1.0 * dp[0][i] / 3);
}
if (dp[1][i] != MAX_INT) {
res = max(res, -1.0 * dp[1][i] / 3);
}
}
return res;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i++)
cin >> b[i];
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
}
dfs(1, 0);
double t = get_max();
cout << fixed << setprecision(10) << 0.25 * t * t << '\n';
return 0;
}
G. Glass Bead Game
题意
有\(n\)个玻璃珠,\(B_1,B_2,\dots,B_n\),每一步你可以选择一个\(B_i\)移到第一个位置上,花费的代价为操作前\(B_i\)前面玻璃珠的个数。现在已知每一步选择玻璃珠\(B_i\)的概率\(p_i\),问当\(m\to \infty\)时,在第\(m\)轮花费的期望代价是多少。
分析
记随机变量\(X\)为第\(m\)轮花费的代价。
记随机变量\(X_{i,j}\)为第\(m\)轮选择\(B_j\)且\(B_i\)在\(B_j\)前面为\(1\),其他情况为\(0\)。
则有\(X=\sum\limits_{i\neq j}X_{i,j}\),故欲求\(E(X)\),需要求\(\sum\limits_{i\neq j}E(X_{i,j})\)。
而\(E(X_{i,j})=P(X_{i,j}=1)\),故只需要求\(\sum\limits_{i\neq j}P(X_{i,j}=1)\)
设\(m\)轮时\(B_i\)在\(B_j\)前面的概率是\(f(m,i,j)\)
有递推式
两边\(m\to \infty\)取极限,可以得到
解得
由于在第\(m\)轮时,选择\(B_j\)的概率是\(p_j\),故有
所以答案就是
代码
#include <iostream>
#include <iomanip>
using namespace std;
const int maxn = 1e2 + 10;
int n;
double p[maxn];
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> p[i];
}
double ans = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (i == j)
continue;
ans += (p[i] * p[j]) / (p[i] + p[j]);
}
}
cout << fixed << setprecision(10) << ans << '\n';
return 0;
}