[CCO2017] 接雨滴

题目描述

晚上,夜黑风高,大雨疯狂地从天而降。

Lucy 想要接住一些雨滴,但她只有有限的工具。她有一套不同高度的柱子来接住雨滴。每根柱子的高度为整数,宽度为 1。她排列好柱子之后,就会用其他器具夹紧柱子,来让雨滴顺利地储存在柱子的间隙里。你可以认为雨滴的数量是无限的。

举个例子,如果 Lucy 有高度分别为 (1,5,2,1,4) 的五根柱子,她可以这样排列柱子。

 *   
 *  *
 *  *
 ** *
*****

这样会接住 5r 雨滴(r 代表 1 个单位的雨滴)。

为了方便表述,我们定义 r 为雨滴的单位。

 *   
 *RR*
 *RR*
 **R*
*****

当然了,她也可以这样摆放柱子,这样可以接住 6r 雨滴。

 *   
 *RR*
 *RR*
**RR*
*****

再举一个例子,如果柱子的高度分别为 (5,1,5,1,5),Lucy 可以接住 8r 雨滴。

*R*R*
*R*R*
*R*R*
*R*R*
*****

最后一个例子,如果柱子的高度分别为 (5,1,4,1,5),她可以接住 9r 雨滴。

*RRR*
*R*R*
*R*R*
*R*R*
*****

Lucy 有 n 个高度为 h1,h2,...,hn 的柱子。她想知道,在所有可能的摆放方案中,所有可能的雨滴量(以 r 为单位)是多少。(具体可看样例解释)

slove

结论:对于某个柱子 x 上方的积水体积,可能产生的所有体积都是合法的。

证明:

首先,形成积水肯定需要两个高柱子,即最高柱和次高柱。我们先放好。接着,我们要插入一个柱子x,考虑x上方能增加多少积水体积。

1)若我们想让x上方没有积水

我们找到柱子y,使得Hy<=HxHy最小并且y上方没有积水。
我们再找一个比y更高的柱子z
image
https://excalidraw.com/
x插在yz之间且紧贴着z
image
因为我们找到的 y 是所有高度不大于 x 号柱子、且上方没有积水的柱子里最低的那一个,因此 z 高度一定大于等于 x (不然找到的就该是 z 了),于是这么插入不会产生新的积水。

y存在的情况下,由于有最高峰,所以z一定存在。如果y不存在,那么不被积水覆盖的柱子都比x高,直接把x放在边界。
image

因此,一定有一种方案使得插入x后总共先不变。

2)设有一个比他高的柱子y,要使得总贡献增加(ayax)
1.如果y没有被积水覆盖

image
除非y是最高峰,一定可以插到y的一侧满足总贡献增加(ayax)

2.如果y被覆盖了。

找到一个离y最近的,最低的且没有被积水覆盖的柱子z.
image
我们把y换为x,贡献变为了(ayax), 把y抽出来,按照1)处理即可。

3)如果y未被插入,从大到小插入就可以。

综上,对于某个柱子 x上方的积水体积,可能产生的所有体积都是合法的。

背包转移,bitset优化。


using namespace std;

const int maxN=5e2+5,maxH=5e1+5;

int a[maxN];
bitset<maxN*maxH>dp,tem;

int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
    {
        scanf("%d",&a[i]);
    }
    sort(a+1,a+1+n);
    dp[0]=true;
    for(int j=1;j<n;++j)
    {
        for(int st=j+1;st<n;++st)
        {
            tem|=(dp<<(a[st]-a[j]));
        }
        dp|=tem;
    }
    for(int i=0;i<=25000;++i)
    {
        if(dp[i])
        {
            printf("%d ",i);
        }
    }
}