题目描述
众所周知,\(cky\)喜欢点外卖。
已知可选的商品有\(n\)种,\(cky\)由于胃容量问题只能点两份(不能一种点两份)。\(cky\)要在防止营养过剩的情况下选择美味度最高的搭配。
具体的,对于每第\(i\)个商品,\(i\)正好是其营养成分,\(s_i\)表示其美味度(商品从\(0\)开始编号)。
对于每种搭配\((a,b)\),其营养程度为(\(a|b\)其中\(|\)表示二进制下的按位或),其美味度为\(s_a+s_b\)。
即\(cky\)要选择满足\(a|b\leq k\)中,\(s_a+s_b\)最大的\((a,b)\)。
由于\(cky\)好久没去体检,所以不知道能接受多少营养成分,所以希望对于每一种\(k\)都求出答案。
为了送分,\(n\)均可以表示为\(2^N\),其中\(N\)为整数。
解题思路
首先,因为是或运算,所以我们可以想到高维前缀和。
我们分别维护对于每一个k的最大值和次大值,那么我们就可以利用高维前缀和的方法进行转移。
我们假设dp[ i ][ 0 / 1 ]表示当前数值i的最大值在原序列中的位置(0),和次大值的位置(1),那么我们就可以枚举给i在二进制下的所有为0的位置选择一个加1,这样就可以转移到一个新的状态tmp,\(tmp=i+(1<<j)\)这样就可以由i推到tmp,我么只要比较i和tmp的最大值和次大值,以下有三种情况:
1)tmp的最大值小于i的最大值:那么我们显然是要把tmp的次大值变成tmp的最大值,把i的最大值变成tmp的最大值。
2)tmp的次大值小于i的最大值并且tmp的最大值和i的最大值取的不是同一个位置:那么tmp的次大值变成i的最大值。
2)tmp的次大值小于i的次大值:那么把tmp的次大值改成i的次大值
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=(1<<21);
int n;
int a[maxn],dp[maxn][2];
int main(){
scanf("%d",&n);
for(register int i=0;i<(1<<n);i++){
scanf("%d",&a[i]);
dp[i][0]=i;
}
for(register int i=0;i<(1<<n);i++){
for(register int j=0;i+(1<<j)<=(1<<n);j++){
if(!(i&(1<<j))){
int tmp=i|(1<<j);
if(a[dp[tmp][0]]<a[dp[i][0]]){
dp[tmp][1]=dp[tmp][0];
dp[tmp][0]=dp[i][0];
}
else if(a[dp[tmp][1]]<a[dp[i][0]]&&dp[tmp][0]!=dp[i][0]){
dp[tmp][1]=dp[i][0];
}
else if(a[dp[tmp][1]]<a[dp[i][1]]){
dp[tmp][1]=dp[i][1];
}
}
}
}
int ans=0;
for(register int i=1;i<(1<<n);i++){
ans=max(ans,a[dp[i][0]]+a[dp[i][1]]);
printf("%d\n",ans);
}
}
原文地址:https://www.cnblogs.com/Fang-Hao/p/9535264.html