• CF1067D Computer Game


    CF1067D

    真的是好题啊,让我对凸包的理解增加了非常非常多...

    DP

    推完式子可以发现,当我们可以升级某一个游戏,我们之后每一轮肯定会升级 (b_i imes p_i) 最大的那个游戏,这样是最优的。

    (B = max(b_i imes p_i)),可以列 DP:

    (f_i) 表示还剩 (i) 轮,目前没有升级过的期望:

    [f_i = displaystyle max_{j=1}^{n}{(1 - p_j) imes f_{i-1} + p_j imes (a_j + B(i - 1))} ]

    这样是 (O(nt)) 显然差的远呢。

    斜率优化

    试试斜率优化,先把 (max) 去掉然后移项:

    (f_i = f_{i-1} - p_jf_{i-1} + p_ja_j + p_jB(i-1))

    [p_j(f_{i-1} - B(i-1)) + f_i - f_{i - 1} = p_ja_j ]

    我们提前按 (p) 排序,就做到了横坐标单调递增。

    为了保持截距 (b) 最大,我们需要维护一个上凸包(即相邻两点之间斜率递减这样一个东西)。

    (A_i = f_{i - 1} - Bi),尝试证明 (A_i) 不增。

    (A_i - A_{i - 1} = f_{i - 1} - f_{i - 2} - B(i-1) + B(i - 2) = f_{i - 1} - f_{i - 2} - B)

    显然两轮的差收益最大是 (B),所以 (A_i - A_{i-1} le 0),证毕。

    这样斜率 (k) 是递减,横坐标是递增了。即每次我们要找到在凸包上 (k(i - 1, i) > k > k(i, i + 1)) 的位置,类似双指针的弹出方式,可以 (O(t)) 了。

    奇怪的优化增加了

    复杂度还是 8 行啊。

    满足这样的斜率优化肯定满足决策单调性,考虑斜率优化的过程就可以。

    这样的话,考虑每一段,都是相同的 (j),那么

    $f_i = f_{i - 1} imes (1 - p_j) + (i - 1) imes p_jB + 1 imes p_ja_j $

    (egin{bmatrix} f_i & i & 1 end{bmatrix} imes egin{bmatrix} 1 - p_j & 0 & 0 \ p_jB & 1 & 0\ p_ja_j& 1& 1 end{bmatrix} = egin{bmatrix} f_{i+1} & i+1 & 1 end{bmatrix})

    这样就可以矩阵快速幂 (O(27log)) 跑一段了。

    那么我们考虑把每一段作为决策的区间找到。

    怎么找呢?

    • 二分,每次二分一个右端点 (r),如何判定合法呢,看 (r) 的转移,如果 (r) 的转移仍为当前的决策,那么整段肯定都是当前决策。((l, r - 1)) 作为 (i) 跑矩阵快速幂,把 (f[r - 1]) 转移到 (f[r]) 看看 (j)(j + 1) 谁优,如果 (j) 更优那么右端点可以继续往右,但是这样每次 check 也要 (log),总复杂度 (O(27 n log^2)) 有点悬。
    • 倍增,类似于 AcWing 109. 天才ACM。先提前把矩阵的 (2^k) 预处理出来,这样从大到小开始倍增,如果能跳就跳就可以了(类似二分的思路),这样可以做到 (O(27 n log))

    Code

    垃圾精度毁我青春,我自闭了。

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    #include <cmath>
    #define x(u) (g[u].p)
    #define y(u) (g[u].p * g[u].a)
    using namespace std;
     
    typedef long long LL;
    
    const double eps = 1e-14;
     
    const int N = 100005, L = 34;
     
    LL T;
     
    int n, q[N], hh = 1, tt = 0;
     
    double B;
     
    struct Game{
    	int a, b;
    	double p;
    	bool operator < (const Game &y) const {
    		if (fabs(y.p - p) < eps) return a * p > y.a * y.p;
    		return p < y.p;	
    	}
    } g[N];
     
    double inline K(int a, int b) {
    	return (y(b) - y(a)) / (x(b) - x(a));
    }
     
    struct Matrix{
    	int n, m;
    	double w[3][3];
    	Matrix operator * (const Matrix b) const {
    		Matrix c; c.n = n, c.m = b.m;
    		memset(c.w, 0, sizeof c.w);
    		for (int i = 0; i < n; i++) 
    			for (int j = 0; j < b.m; j++) 
    				for (int k = 0; k < m; k++)
    					c.w[i][j] += w[i][k] * b.w[k][j];
    		return c;
    	}
    } res, A[L];
     
    Matrix inline build(int j) { 
    	Matrix c; memset(c.w, 0, sizeof c.w);
    	c.n = 3, c.m = 3;
    	c.w[0][0] = 1 - g[j].p, c.w[1][0] = g[j].p * B, c.w[2][0] = g[j].p * g[j].a;
    	c.w[1][1] = c.w[2][1] = c.w[2][2] = 1;
    	return c;
    }
     
    double inline get(double S, int j) {
    	return g[j].p * (g[j].a - S);
    }
     
    int main() {
    	scanf("%d%lld", &n, &T);
    	for (int i = 1; i <= n; i++) {
    		scanf("%d%d%lf", &g[i].a, &g[i].b, &g[i].p);
    		B = max(B, g[i].b * g[i].p);
    	}
    	sort(g + 1, g + 1 + n);
    	for (int i = 1; i <= n; i++) {
    	    if (i > 1 && fabs(g[i].p - g[i - 1].p) < eps) continue;
    	    while (hh < tt && K(q[tt - 1], q[tt]) <= K(q[tt], i)) tt--;
    		q[++tt] = i;
    	}
    	res.n = 1, res.m = 3, res.w[0][2] = 1;
    	LL x = 0; double k = 0;
    	while (x != T) {
    		while (hh < tt && get(k, q[hh]) < get(k, q[hh + 1])) hh++;
    		A[0] = build(q[hh]);
    		for (int i = 1; i < L; i++) A[i] = A[i - 1] * A[i - 1];
    		for (int i = L - 1; ~i; i--) {
    			if (x + (1ll << i) < T) {
    				Matrix C = res * A[i];
    				double nK = C.w[0][0] - B * C.w[0][1];
    				if (hh == tt || get(nK, q[hh]) > get(nK, q[hh + 1]))
    					res = C, x += 1ll << i;
    			} 
    		}
    		res = res * A[0], k = res.w[0][0] - B * res.w[0][1], ++x;
    	}		
    	printf("%.16f
    ", res.w[0][0]);
    	return 0;
    }
    

    总结

    这题让我学会了...

    • 凸包上凸壳怎么维护
    • 从决策来考虑区间转移,每一段快速算出(考虑快速幂、倍增等 log 算法),神奇的优化。
  • 相关阅读:
    如何搭建docker镜像私有仓库
    CentOS 安装 Docker教程
    git push撤销、git commit 撤销、git add撤销、修改git提交信息
    虚拟对抗训练(VAT):一种用于监督学习和半监督学习的正则化方法
    CentOS7虚拟机配置、Hadoop搭建、wordCount DEMO运行
    统计学习方法——KD树K近邻搜索
    应用程序使用统计信息 – .NET CORE(C#) WPF界面设计
    (四)开源C# WPF控件库《AduSkin – UI》
    简化MVVM属性设置和修改
    少量代码设计一个登录界面(二) – .NET CORE(C#) WPF开发
  • 原文地址:https://www.cnblogs.com/dmoransky/p/13670141.html
Copyright © 2020-2023  润新知