专栏文章

CF 303E Random Ranking

CF303E题解参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@miovpryg
此快照首次捕获于
2025/12/03 01:55
3 个月前
此快照最后确认于
2025/12/03 01:55
3 个月前
查看原文
首先对于浮点数的随机,可以忽略 == 的情况。
如果知道了 xx 的排名为 yy,那么就说明除 xx 外有 y1y - 1 个数比 xx 小,这说明在对于单个数考虑时我们只关系其他数比这个数大还是小。
那么如果知道了 xx 的值 vv,会发现对于每个其他的数都可以知道比这个数小和大的概率,那么应该可以写成一个 pix+(1pi)p_ix + (1 - p_i) 的形式并卷起来。
不过现在的问题是 vv 的值可能是很多样的,这该怎么办?
发现其实 pip_i 也是可以表示成带 vv 的多项式的,不过会发现 v<li,li<v<ri,ri<vv < l_i, l_i < v < r_i, r_i < v 三种情况的 pip_i 是不同的。
于是这启发对 l1n,r1nl_{1\sim n}, r_{1\sim n} 保留下来的极小区间 [L,R][L, R] 单独求解。
不过这样做在最后会遇到求 E(vp)(0p<n)E(v^p)(0\le p < n) 这个问题,积分后得到的幂次太大或许精度会很烂,我并没有尝试过。
此时考虑另外一个想法,不去考虑分析 vv 的具体求值,而是只当作 vv 在这个区间 (L,R)(L, R) 里。
不过这就有问题了,如果一个数的值 <L< L 或者 >R> R 肯定是好算的,但是如果这个数的值也在 (L,R)(L, R) 内怎么办?
有以下结论:若有 cc 个实数是在同一范围内均匀随机的,那么每一个数在 1c1\sim c 的排名的概率都是 1c\frac{1}{c}
感性理解一下,因为不用考虑相等的关系,就可以当作是随机一个排列代表这 cc 个数的大小关系,就可以得到这个值了。
于是考虑枚举 xx 后直接 dp 记 fi,x,yf_{i, x, y} 表示考虑前 ii 个数中有 xx 个数 <L< L,有 yy 个数在 (L,R)(L, R) 之间的方案数。
转移直接暴力枚举这一个数的三种情况即可,可以做到 O(n5)\mathcal{O}(n^5)
要做到更优,可以考虑缺一分治,可以参考这篇题解做到 O(n4logn)\mathcal{O}(n^4\log n)
有一个想法是通过背包撤销做到 O(n4)\mathcal{O}(n^4),不过因为带除法,精度感觉也并不是很友好。
我的代码是 O(n5)\mathcal{O}(n^5) 的,听说比较卡常,实际上我把最后求解答案由暴力枚举变成差分就过了。
C
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>

constexpr double eps = 1e-9;

constexpr int maxn = 80;

int n, m;
int l[maxn], r[maxn], val[maxn * 2];

double low[maxn], eq[maxn], upp[maxn];

double f[maxn][maxn];
double ans[maxn][maxn];

int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%d%d", &l[i], &r[i]);
        val[i << 1] = l[i], val[i << 1 | 1] = r[i];
    }
    std::sort(val, val + n * 2);
    m = std::unique(val, val + n * 2) - val;
    
    for (int s = 0; s + 1 < m; s++) {
        const int sl = val[s], sr = val[s + 1];
        for (int i = 0; i < n; i++) {
            if (l[i] <= sl && sr <= r[i]) {
                low[i] = 1.0 * (sl - l[i]) / (r[i] - l[i]);
                eq[i] = 1.0 * (sr - sl) / (r[i] - l[i]);
                upp[i] = 1.0 * (r[i] - sr) / (r[i] - l[i]); 
            } else if (r[i] <= sl) {
                low[i] = 1.0, eq[i] = upp[i] = 0.0;
            } else {
                upp[i] = 1.0, eq[i] = low[i] = 0.0;
            }
        }
        
        for (int u = 0; u < n; u++) {
            if (eq[u] < eps) continue;
            
            for (int x = 0; x < n; x++) {
                for (int y = 0; x + y < n; y++) f[x][y] = 0.0;
            }
            f[0][0] = eq[u];
            
            for (int i = 0, lim = 0; i < n; i++) {
                if (u == i) continue;
                
                lim++;
                if (upp[i] > 1 - eps) continue;
                if (low[i] > 1 - eps) {
                    for (int x = lim; x >= 0; x--) {
                        for (int y = 0; x + y <= lim; y++) {
                            f[x][y] = x ? f[x - 1][y] : 0.0;
                        }
                    }
                    continue;
                }
                
                for (int x = lim; x >= 0; x--) {
                    for (int y = lim - x; y >= 0; y--) {
                        f[x][y] *= upp[i];
                        if (x) f[x][y] += f[x - 1][y] * low[i];
                        if (y) f[x][y] += f[x][y - 1] * eq[i];
                    }
                }
            }
            
            for (int x = 0; x < n; x++) {
                for (int y = 0; x + y < n; y++) {
                    const double prob = f[x][y] / (y + 1);
                    ans[u][x] += prob, x + y + 1 < n && (ans[u][x + y + 1] -= prob, 1);
                }
            }
        }
    }
    
    for (int i = 0; i < n; i++) {
        printf("%.10lf ", ans[i][0]);
        for (int j = 1; j < n; j++) {
            printf("%.10lf ", ans[i][j] += ans[i][j - 1]);
        }
        puts("");
    }
    return 0;
}

评论

0 条评论,欢迎与作者交流。

正在加载评论...