A 【UR #17】滑稽树上滑稽果 [* easy]
给定 (n) 个数 (a_i),将他们排成一棵树,定义 (i) 的权值为 (i) 到链顶的点权的 (mathbf{and}) 的值。
求最小权值和。
(nle 10^5,a_ile 2 imes 10^5)
Solution
显然最优解是变成链,而且通过不超过 (log w) 个元素就可以变成所有元素的 (mathbf{and}) 值。
可以认为问题等价于现在有全集,使用 (i) 个元素,将其缩小到 (mathbf{and}) 和的最小花费。
然后答案就是 (f_{i}+(n-i) imes {mathbf{and}})
考虑计算 (f_{i,S}) 表示用了 (i) 个元素,(mathbf{and}) 为 (S) 的最优解((ile log w))
于是得到了一个 (mathcal O(nwlog w)) 的做法。
考虑优化,我们发现 (S) 只会从大的转移到小的,将 (i) 这个维度压掉,然后从大到小转移。
枚举 (S) 的子集 (T) 这样 (S o T) 当且仅当存在一个元素为 ((Tland 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 ;
}