[CCO 2017]接雨滴

[CCO 2017]接雨滴 [* interesting]

题意

挺有意思的。

首先计算水滴量是不好的,可以考虑计算水滴量加上柱子高度的和。

首先不难注意到,我们在最大值的左侧和右侧同时添加权值是没有意义的,因为可以通过调整将他们调整至一边。(生成答案的部分单调排序即可)

所以可以规定最大值在第一个位置。

紧接着,我们发现答案可以近似视为选出次大值和一些元素,将他们从大到小排序,然后将其余元素插入在中间部分。

因为假设 \(i\) 比两端小,那么 \(i\) 是没有意义的,所以有效的元素一定是单调的,同时根据前文所述,强制让最大值位于 \(1\) 号位置后,后续的元素一定会构成一个单调栈。

这样可以从大到小将权值依次填入,不难发现我们此时对答案的描述即为 \(\sum c_i\times pre_i\)

于是设 \(f_{k,i,j}\) 表示考虑到第 \(k\) 个权值,当前考虑到位置 \(i\),填入的权值和为 \(j\) 是否可行,转移只需要枚举 \(t\) 然后将 \(f_{k-1,i-t,j-t\times c}\) 转移过来即可。

不难发现转移本质上是一个完全背包,所以可以直接从前往后从 \(f_{k,i-1,j-c}\) 处转移。

通过 bitset 优化,容易得到一个 \(\mathcal O(\frac{n^2\sum h}{\omega})\) 的解法。

发现 \(h\) 非常小,同时不难发现每种 \(h\) 只需要转移一次,所以可以得到一个 \(\mathcal O(\frac{nh\sum h}{\omega})\) 的做法。(近似于 \(\mathcal O(\frac{n^2h^2}{\omega})\)

唯一需要注意的就是为了确保大于 \(j\) 的元素一定可以被塞下去,对于第 \(i\) 大的权值我们需要从 \(f_{k-1,i}\) 处开始转移。

\(Code:\)

#include<bits/stdc++.h>
using namespace std ;
#define Next( i, x ) for( register int i = head[x]; i; i = e[i].next )
#define rep( i, s, t ) for( register int i = (s); i <= (t); ++ i )
#define drep( i, s, t ) for( register int i = (t); i >= (s); -- i )
#define re register
int gi() {
	char cc = getchar() ; int cn = 0, flus = 1 ;
	while( cc < '0' || cc > '9' ) {  if( cc == '-' ) flus = - flus ; cc = getchar() ; }
	while( cc >= '0' && cc <= '9' )  cn = cn * 10 + cc - '0', cc = getchar() ;
	return cn * flus ;
}
const int N = 500 + 5 ; 
const int M = 25000 + 5 ; 
int n, S, h[N] ; 
bitset<M> f[N], ans ; 
signed main()
{
	n = gi() ; 
	rep( i, 1, n ) h[i] = gi(), S += h[i] ; 
	sort(h + 1, h + n + 1, greater<int>()) ; 
	f[1][h[1]] = 1 ; 
	rep( i, 2, n ) {
		if( (i != 2) && (h[i] == h[i - 1]) ) continue ; 
		rep( j, i, n ) f[j] |= (f[j - 1] << h[i]) ;
		ans |= f[n] ; 
	}
	for(re int i = S; i <= M - 5; ++ i)
	if( ans[i] ) cout << i - S << " " ; puts("") ; 
	return 0 ;
}
posted @ 2020-10-22 23:04  Soulist  阅读(371)  评论(0编辑  收藏  举报