【UR #17】滑稽树上滑稽果

A 【UR #17】滑稽树上滑稽果 [* easy]

给定 \(n\) 个数 \(a_i\),将他们排成一棵树,定义 \(i\) 的权值为 \(i\) 到链顶的点权的 \(\mathbf{and}\) 的值。

求最小权值和。

\(n\le 10^5,a_i\le 2\times 10^5\)

Solution

显然最优解是变成链,而且通过不超过 \(\log w\) 个元素就可以变成所有元素的 \(\mathbf{and}\) 值。

可以认为问题等价于现在有全集,使用 \(i\) 个元素,将其缩小到 \(\mathbf{and}\) 和的最小花费。

然后答案就是 \(f_{i}+(n-i)\times {\mathbf{and}}\)

考虑计算 \(f_{i,S}\) 表示用了 \(i\) 个元素,\(\mathbf{and}\)\(S\) 的最优解(\(i\le \log w\)

于是得到了一个 \(\mathcal O(nw\log w)\) 的做法。

考虑优化,我们发现 \(S\) 只会从大的转移到小的,将 \(i\) 这个维度压掉,然后从大到小转移。

枚举 \(S\) 的子集 \(T\) 这样 \(S\to T\) 当且仅当存在一个元素为 \((T\land S)\land U\) 的子集,显然如果存在那么 \(S\) 可以去往更小的 \(T\),在此 \(T\) 处更新答案不会使得答案更劣。

那么从小到大预处理一次,然后再从大往小 dp 一边即可。

复杂度为 \(\mathcal O(3^{18})\)

\(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 Inf = 1e15 ; 
const int N = (1 << 18) + 5 ; 
int n, limit, A, a[N], w[N] ;
long long f[N] ; 
signed main()
{
	n = gi(), limit = (1 << 18) - 1 ; 
	rep( i, 1, n ) a[i] = gi(), w[a[i]] = 1 ; 
	A = a[1] ;
	rep( i, 2, n ) A &= a[i] ; 
	memset( f, 63, sizeof(f) ) ; 
	rep( i, 1, n ) f[a[i]] = 1ll * a[i] + 1ll * (n - 1) * A ; 
	rep( j, 1, limit ) for(re int k = 1; k <= limit; k <<= 1)
		if(j & k) w[j] |= w[j ^ k] ; 
	drep( S, 0, limit ) {
		if( f[S] > Inf ) continue ; 
		for(re int i = S; ; i = ((i - 1) & S) ) {
			int u = (i | (S ^ limit) ) ; 
			if( w[u] ) f[i] = min( f[i], f[S] + (i - A) ) ;
			if( !i ) break ; 
		}
	}
	cout << f[A] << endl ; 
	return 0 ;
}
posted @ 2020-10-26 22:37  Soulist  阅读(165)  评论(0编辑  收藏  举报