第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)=\left\{ \begin{aligned} &0,&T\in S\\ &\frac{n+\sum_{i}f(T\cup i)}{n},&T\notin S \end{aligned} \right. \]

由于\(f(T\cup i)\)可能就是\(f(T)\),故将\(f(T)\)移到一边,解得

\[f(T)=\left\{ \begin{aligned} &0,&T\in S\\ &\frac{n+\sum_{i\notin T}f(T\cup i)}{n-|T|},&T\notin S \end{aligned} \right. \]

很容易使用状压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\)维测度为

\[V_n=\frac{x^n}{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\)的可能,计算结束,故式子如下

\[V_n=\sum\limits_{i=0}^{\lfloor\frac{1}{a}\rfloor}\frac{(-1)^{i}}{n!}\binom{n}{i}(1-ia)^n \]

计随机变量\(X\)为每次均匀随机走\([0,a]\),达到大于等于\(1\)所需的最少次数,则有

\[\begin{aligned} P(X\leq k)&=1-\frac{1}{a^k}\sum\limits_{i=0}^{\lfloor\frac{1}{a}\rfloor}\frac{(-1)^{i}}{k!}\binom{k}{i}(1-ia)^k\\ &=1-\sum\limits_{i=0}^{\lfloor\frac{1}{a}\rfloor}\frac{(-1)^{i}}{k!}\binom{k}{i}\left(\frac{1}{a}-i\right)^k \end{aligned} \]

由此可得

\[\begin{aligned} P(X = k)&=P(X \leq k) - P(X \leq k-1)\\ &=\sum\limits_{i=0}^{\lfloor\frac{1}{a}\rfloor}\frac{(-1)^{i}}{(k-1)!}\binom{k-1}{i}\left(\frac{1}{a}-i\right)^{k-1}-\sum\limits_{i=0}^{\lfloor\frac{1}{a}\rfloor}\frac{(-1)^{i}}{k!}\binom{k}{i}\left(\frac{1}{a}-i\right)^k \end{aligned} \]

故期望为

\[\begin{aligned} E(X)&=\sum\limits_{k=1}^{\infty}kP(X=k)\\ &=\sum\limits_{k=1}^{\infty}k\left(\sum\limits_{i=0}^{\lfloor\frac{1}{a}\rfloor}\frac{(-1)^{i}}{(k-1)!}\binom{k-1}{i}\left(\frac{1}{a}-i\right)^{k-1}-\sum\limits_{i=0}^{\lfloor\frac{1}{a}\rfloor}\frac{(-1)^{i}}{k!}\binom{k}{i}\left(\frac{1}{a}-i\right)^k\right)\\ &=\sum\limits_{k=0}^{\infty}\sum\limits_{i=0}^{\lfloor\frac{1}{a}\rfloor}\frac{(-1)^{i}}{k!}\binom{k}{i}\left(\frac{1}{a}-i\right)^k\\ &=\sum\limits_{i=0}^{\lfloor\frac{1}{a}\rfloor}\sum\limits_{k=0}^{\infty}\frac{(-1)^{i}}{k!}\binom{k}{i}\left(\frac{1}{a}-i\right)^k\\ &=\sum\limits_{i=0}^{\lfloor\frac{1}{a}\rfloor}\frac{(-1)^i}{i!}\sum\limits_{k=0}^{\infty}\frac{1}{(k-i)!}\left(\frac{1}{a}-i\right)^k\\ &=\sum\limits_{i=0}^{\lfloor\frac{1}{a}\rfloor}\frac{(-1)^i}{i!}\left(\frac{1}{a}-i\right)^{i}\sum\limits_{k=0}^{\infty}\frac{1}{(k-i)!}\left(\frac{1}{a}-i\right)^{k-i}\\ &=\sum\limits_{i=0}^{\lfloor\frac{1}{a}\rfloor}\frac{(-1)^i}{i!}\left(\frac{1}{a}-i\right)^{i}\mathrm{e}^{\frac{1}{a}-i} \end{aligned} \]

方法二

\(f(x)\)为每次均匀随机走\([0,a]\),达到大于等于\(x\)的期望次数,由全期望公式

\[f(x)=\int_{x-a}^x\left(\frac{f(t)+1}{a}\right)\mathrm{d}t=1+\int_{x-a}^x\frac{f(t)}{a}\mathrm{d}t \]

两侧对\(x\)求导

\[af'(x)=f(x)-f(x-a) \]

对于这个式子,有两种处理办法,一种是利用导数的定义进行近似计算,另一种是像方法一那样,通过这个式子,推出最终结果的式子。

法一

进行近似计算,当\(\Delta x\to 0\)时,有

\[a\cdot \frac{f(x+\Delta x)-f(x)}{\Delta x}=f(x)-f(x-a)\\ f(x+\Delta x)=f(x)+\frac{\Delta x}{a}(f(x)-f(x-a))\\ \]

令初值\(f(0)=1\)\(f(1)\)即为答案

由于题目是多组输入,且步长\([0,a]\),大于等于\(1\)的期望步数,等价于,步长\([0,1]\),大于等于\(\frac{1}{a}\)的期望步数,故而把递推式变成

\[f(x+\Delta x)=f(x)+\Delta x(f(x)-f(x-1))\\ \]

令初值\(f(0)=1\)\(f(\frac{1}{a})\)即为答案,这样在多组输入之前就可以进行预处理。

法二

像法一一样先转化为步长为\([0,1]\)的问题,递推式变成

\[f'(x)=f(x)-f(x-1) \]

\(x\)范围进行分类讨论

  1. \(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\)

  2. \(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}\)

  3. \(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\)为非负整数)

\[f(x)=\sum\limits_{i=0}^k\frac{(-1)^i}{i!}(x-i)^i\mathrm{e}^{x-i} \]

\(x=\frac{1}{a}\),得

\[\sum\limits_{i=0}^{\lfloor\frac{1}{a}\rfloor}\frac{(-1)^i}{i!}\left(\frac{1}{a}-i\right)^i\mathrm{e}^{\frac{1}{a}-i} \]

代码

代式子

#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}(-x^2+a_u x)}{|V|}=-x^2+\frac{\sum_{u \in v}a_u}{|V|}x \]

它是二次函数,最大值为

\[\frac{1}{4}\left(\frac{\sum_{u \in v}a_u}{|V|}\right)^2 \]

故只需要让\(\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)\)

有递推式

\[f(m,i,j)=(1-p_j)f(m-1,i,j)+p_if(m-1,j,i)\\ f(m,i,j)=(1-p_j)f(m-1,i,j)+p_i(1-f(m-1,i,j)) \]

两边\(m\to \infty\)取极限,可以得到

\[f(i,j)=(1-p_j)f(i,j)+p_i(1-f(i,j)) \]

解得

\[f(i,j)=\frac{p_i}{p_i+p_j} \]

由于在第\(m\)轮时,选择\(B_j\)的概率是\(p_j\),故有

\[P(X_{i,j}=1)=\frac{p_i p_j}{p_i+p_j} \]

所以答案就是

\[\sum\limits_{i\neq j}\frac{p_i p_j}{p_i+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;
}
posted @ 2022-04-21 14:17  聆竹听风  阅读(655)  评论(0)    收藏  举报