题解:P13552 鱼类考古学
int4399
·
·
题解
题意:有 n 个数,要进行 n-k 次按位与和 k-1 次加法,求最后结果的最大值。
对于这种有多种操作的问题,看能不能推出最优解长啥样。我们发现如果两个数进行加法,那么它之后就不会再按位与了,因为 (a+b)\operatorname{and} c\le c\le c+(a\operatorname{and} b),这两个用的符号都一样,所以最优解肯定是:先按位与再加。
问题转化:将 n 个数划分为 k 个集合,该划分的权值为所有组的按位与之和,求最大权值。
我们再挖掘最优解的性质,发现这个东西可以按位贪心,因为如果一个集合第 k 位均为 1,你把一个该位不为 1 的数换过来,一定减少 2^k,至多增加 2^k-1,这个可以通过极端思想考虑出来,也就是先考虑高位不劣。
于是问题再次转化:将 n 个 0/1 划分为 k 个集合,该划分的权值为所有组的按位与之和,求最大权值。
继续分析,把 0 和 1 放到一组一定不优,因为调整 1 到别的集合可能答案会更大。答案上界是 k,当 1 的个数小于等于 k 时直接每个 1 单开一组一定最优。
总结一下流程,就是从高位到低位考虑,若该位 $1$ 的个数小于等于 $k$,该位的 $1$ 全单开集合;否则所有的 $0$ 扔一个集合。
代码:
```
#include<bits/stdc++.h>
using namespace std;
int T,n,k,a;long long ans;
void solve(vector<int> S,int k,int hb){
if(hb<0) return;int b=0,r=-1;vector<int> s;
for(int v:S) b+=v>>hb&1;
if(b<k){for(int v:S){if(v>>hb&1) ans+=v;else s.push_back(v);}return solve(s,k-b,hb-1);}
ans+=(1ll<<hb)*(k-1);
for(int v:S){if(v>>hb&1) s.push_back(v-(1<<hb));else r=r==-1?v:r&v;}
if(r!=-1) s.push_back(r);
else ans+=1<<hb;
solve(s,k,hb-1);
}
void _main(){
cin>>n>>k,ans=0;vector<int> s;
for(int i=1,a;i<=n;i++) cin>>a,s.push_back(a);
solve(s,k,30),cout<<ans<<'\n';
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>T;
while(T--) _main();
return 0;
}
```