P1721 [NOI2016] 国王饮水记

传送门


这题只能说太妙了,于是直接贺掉了

这边建议移步到这里,有更为严谨详细的证明

下面的定理都是 PPT 上的定理,但证明略有不同,有一些自己的理解在里面


基础定理

  1. 对于所有水量低于 \(h_1\) 的城市,肯定不在我们的考虑范围内

不然的话还要拿 \(h_1\) 的水去分给这些城市,那不是白给吗

这也说明了 \(h_1\) 是不断增大的

注:后面的证明都默认不再考虑小于 \(h_1\) 的城市

  1. 除了城市 \(1\),每个城市最多只会被连接一次,而且每次操作都包含城市 \(1\)

假设我们先连接了某些城市,但不包括城市 \(1\),设这个城市集为 \(C\)

如果我们第二次操作是 \(C\) 加上城市 \(1\),那么这显然是与第一次直接连接 \(C\) 中的所有城市和城市 \(1\) 是等价的

如果第二次操作是 \(C\) 中的某些城市(假设选了 \(k\) 个城市)加上城市 \(1\),显然不可能优于第一次直接连接 \(C\) 中最高的前 \(k\) 个城市和城市 \(1\)

而对于每个城市,如果它已经和城市 \(1\) 连接过了,那么它以后的 \(h_i\) 都是小于等于 \(h_1\) 的,再次连接显然不优

  1. 如果操作次数足够,最优方案为:将所有城市从小到大排序,然后与 \(h_1\) 依次 连接

从小到大:由 定理2,我们得知每次城市用过一次后都会作废,那我们要让更多的城市的水量流入 \(h_1\),那显然是水量越小的越靠前,才能让它们的水量流入 \(h_1\)

依次:假如现在有 \(h_1<h_2<h_3\) ,如果一次性连接的话最后水量为 \(\frac{h_1+h_2+h_3}{3}\),依次连接的话最后水量为 \(\frac{h_1+h_2}{4}+\frac{h_3}{2}\)

现在我们将这两个结果作差,得到 \(\frac{h_1}{12}+\frac{h_2}{12}-\frac{h_3}{6}<0\),因此“依次连接”是更优的;多个城市的时候同理


进阶定理

注:以下的证明涉及的城市均不含 \(h_1\),且城市已经排好序

让我们回到操作数 \(<\) 城市数的情况,这时候说明我们需要同时连接多个城市了

  1. 每次操作的最低水量一定会大于上一次操作的最大水量

证明和定理3从小到大是相似的,如果两个相邻的操作中存在逆序的水量,显然将它们交换一下会更优

  1. 每次操作一定是连续的一段城市

否则我们一定可以将这次操作中水量最小的城市替换成中间断点的某个城市,显然更优

  1. 相邻两次操作的区间一定相邻(即操作紧密

否则我们可以将前一次操作的区间整体向右移动,直到与后一次操作的区间相邻,这样会更优

到这里,我们就可以发现一个 dp 做法了,我们令 \(sum[k] = \sum_{i=1}^k h[i]\),那么有转移方程为:

\[dp_{i,j}=max\{\frac{dp_{k,j-1}+sum[i]-sum[k]}{i-k+1}\} \]

其中 \(i\) 表示第 \(i\) 个城市,\(j\) 表示操作次数(分段数),\(k\) 表示转移点

这个显然是一个斜率方程,也就是我们要最大化过点 \((i, sum[i])\) 的直线的斜率,我们只需要三分凸壳上的最低点即可

到这里复杂度是 \(O(nkp\ logn)\)

  1. 如果城市 \(i\) 的决策点 \(k\) 优于 决策点 \(s<k\),那么城市 \(j>i\) 的决策点 \(k\) 同样优于 \(s\) ,也就是决策单调

说实话这个我真的不可能想得到

此时应有不等式

\[(dp_{k,j-1}+sum[i]-sum[k])(i - s + 1)\ge(dp_{s,j-1}+sum[i]-sum[s])(i - k + 1) \]

经过漫长的整理,得:

\[(k-s)(sum_i - sum_k + dp_{k,j - 1})\ge(i - k + 1)(sum_k - sum_s+dp_{s,j - 1}-dp_{k,j - 1}) \]

由定义可得,\(dp_{k,j - 1}\ge dp_{l,j - 1}\),且 \(h_{i+1}>h_{i}>...>h_k>...>h_l\),等式两遍加上 \((k-l)h_{i+1}\)

\[\begin{aligned} (k-s)(sum_{i+1} - sum_k + dp_{k,j - 1}) &\ge (i - k + 1)h_k+(k-s)h_{i+1}-(i - k + 1)(dp_{k,j - 1}-dp_{k - 1, j - 1})\\ &\ge (i - k + 1)(h_k-(dp_{k,j - 1} - dp_{k - 1, j - 1})) \end{aligned} \]

证毕 (说实话我有点看不懂QAQ)

到这我们就可以将每次寻找决策点的过程均摊为 \(O(1)\)

时间复杂度为 \(O(nkp)\)


最终定理

也就是我不会证明的定理

  1. 每次操作的区间长度不会超过上一次的长度

如果有一次操作的区间大于上一次操作的区间,我们就将这次的区间的第一个城市移动到上一次操作中,方案会更优

至于为什么,我不知道,自己打表验证吧......

  1. 所有水量互不相同的情况下,操作区间长度大于 \(1\) 的不会超过 \(log\ \frac{nh}{min(h_{i+1} - h_i)}\),上界为 \(14\)

不会证明,需要的移步讲课PPT


代码:

这题的坑点就是,他帮你写的定点高精的小数部分数组只开了 \(234\) 位,但是他最后一个点要求输出 \(3000\) 位,所以还要自己手动增大......(把我坑得wa了5次)

#include<iostream>
#include<fstream>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#define LL long long
inline int reads()
{
    int sign = 1, re = 0; char c = getchar();
    while(c < '0' || c > '9'){if(c == '-') sign = -1; c = getchar();}
    while('0' <= c && c <= '9'){re = re * 10 + (c - '0'); c = getchar();}
    return sign * re;
}
struct Point
{
    double x, y;
}p[8005];
inline double slope(Point a, Point b) {return (a.y - b.y) / (a.x - b.x);}
int n, k, P, h[8005], tot, sum[8005];
int q[8005], he, ta, fin, pos;
double dp[8005][20], Mx; int fo[8005][20];
Decimal ans;
Decimal get_de(int i, int j)
{
    if(!j) return h[1];
    return (get_de(fo[i][j], j - 1) + sum[i] - sum[fo[i][j]]) / (i - fo[i][j] + 1);
}
signed main()
{
#ifndef ONLINE_JUDGE
    freopen("test.in", "r", stdin);
    freopen("test.out", "w", stdout);
#endif
    n = reads(), k = reads(), P = reads(); h[tot = 1] = reads();
    for(int i = 2; i <= n; i++)
    {
        int a = reads();
        if(a > h[1]) h[++tot] = a;
    }
    std::sort(h + 1, h + 1 + tot); k = std::min(k, tot);
    for(int i = 1; i <= tot; i++) sum[i] = sum[i - 1] + h[i], dp[i][0] = h[1];
    for(int j = 1; j <= std::min(k, 14); j++)
    {
        q[he = ta = 1] = 1;
        for(int i = 1; i <= tot; i++) p[i] = (Point){i - 1, sum[i] - dp[i][j - 1]};
        for(int i = 2; i <= tot; i++)
        {
            Point now = (Point){i, sum[i]};
            while(ta > he && slope(now, p[q[he]]) < slope(now, p[q[he + 1]])) he++;
            dp[i][j] = (dp[q[he]][j - 1] + sum[i] - sum[q[he]]) / (i - q[he] + 1); fo[i][j] = q[he];
            while(ta > he && slope(p[q[ta - 1]], p[q[ta]]) >= slope(p[i], p[q[ta]])) ta--;
            q[++ta] = i;
        }
    }
    fin = tot - k + std::min(k, 14);
    for(int i = 0; i <= std::min(k, 14); i++)
        if(dp[fin][i] > Mx) Mx = dp[fin][i], pos = i;
    ans = get_de(fin, pos);
    for(int i = fin + 1; i <= tot; i++) ans = (ans + h[i]) / 2;
    std::cout << ans.to_string(P << 1);
    return 0;
}
posted @ 2022-03-17 15:51  zuytong  阅读(119)  评论(0编辑  收藏  举报