Muzyka pop
给定 (n) 个整数 (a_1, a_2, dots, a_n) 和一个整数 (m)。请找到 (n) 个非负整数 (b_1, b_2, ldots, b_n),满足 (0 le b_1 < b_2 < ldots < b_n le m) 并且 (sumlimits_{i=1}^{n} ext{popcount}(b_i) cdot a_i) 的值最大,其中 ( ext{popcount}(x)) 为 (x) 在二进制下的 (1) 的个数。
(1 le n le 200, n - 1 le m le 10^{18}, 1 le |a_i| le 10^{14})
题解
http://jklover.hs-blog.cf/2020/06/08/Loj-3215-Muzyka-pop/#more
数位 dp.
假定有一棵插入了 ([0,m]) 内所有数的 01 Trie 树,我们可以在上面做简单的 dp 求出答案.
设 (f(x,l,r)) 表示把 ([l,r]) 内所有的 (b) 都分配入 (x) 的子树中能产生的最大贡献.
转移时枚举将 ([l,k]) 内的 (b) 分入 (x) 左子树中, ([k+1,r]) 内的 (b) 分入 (x) 右子树中.
[f(x,l,r) =max_{l-1le kle r} f(x_{lson},l,k) + f(x_{rson},k+1,r)+sum_{i=k+1}^r a_i
]
但这棵 Trie 树的节点数目是 (O(m)) 的,直接这样 dp 不可行.
注意到除去从 (m) 到根这条链上的点,其它点的每个子树都是完全二叉树.
于是 dp 只需要记录当前节点深度,以及是否在从 (m) 到根的链上.
其实这东西就是个数位 dp, 深度表示考虑到了哪一位,是否在链上表示是否顶住了上界.
用 Trie 树的形式可能比较容易理解贡献的计算.
时间复杂度 (O(n^3log m)) .
CO int N=210;
CO int64 inf=1e18;
int64 sum[N],f[60][2][N][N];
int main(){
int n=read<int>();
int64 m=read<int64>();
int K=log2(m);
for(int i=1;i<=n;++i) sum[i]=sum[i-1]+read<int64>();
for(int i=0;i<=K;++i)for(int lim=0;lim<=1;++lim){
int t=lim and ~m>>i&1; // unable to go right
for(int l=1;l<=n;++l)for(int r=l;r<=n;++r){
int mi=t?r:l-1;
int64 val=-inf;
for(int k=mi;k<=r;++k){
int64 tmp=sum[r]-sum[k];
if(i){
tmp+=f[i-1][t][l][k];
tmp+=f[i-1][lim][k+1][r];
}
else if(k-l+1>1 or r-k>1) continue; // pairwise distinct
val=max(val,tmp);
}
f[i][lim][l][r]=val;
}
}
printf("%lld
",f[K][1][1][n]);
return 0;
}