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

link

本来还以为每次取最优的是对的,结果题解里说部分分给了这个做法那一定是错的。。

考虑到这个题,如果我们算出所有数的按位 and 和,设他为 x ,那么答案每一个数一定包含 x,于是我们可以先把答案加上 n×x,再把每个数减掉 x ,那么所有数的按位 and 和酒变成 0 了。

再来考虑怎样最优,我们发现一旦变成 0,后面的答案就全部都是 0,所以其实就是在让我们求最小的一个集合,他的所有元素按位 and 和为 0

考虑 dp,设 fSS 集合中的位变成 0,其他位不管的最小代价,那么答案就是 fmaxn

考虑转移,我们每次枚举一个数加入这个集合, fS=min(fS,fS&ai+(S&ai))

这样虽然会加入重复的数,但是一定不优,不用考虑。

于是得到一个 O(n2) 的算法。

考虑优化,我们每次枚举 S 的子集 T ,强制让 T 变为 0

那么问题就转化为判断有没有数 T 集合全是 0,可以 fwt 来解决。

复杂度就变成了 O(3logn2)=O(nlog32),可以通过。

Code

Copy
#include<bits/stdc++.h> #define N 2001001 #define MAX 2001 using namespace std; typedef long long ll; typedef double db; const ll mod=998244353,inf=1e18,inv2=(mod+1)/2; inline void read(ll &ret) { ret=0;char c=getchar();bool pd=false; while(!isdigit(c)){pd|=c=='-';c=getchar();} while(isdigit(c)){ret=(ret<<1)+(ret<<3)+(c&15);c=getchar();} ret=pd?-ret:ret; return; } ll n,a[N],b[N],maxn,tmp,f[N]; inline void fwt(ll a[],ll n) { for(int mid=1;mid<n;mid<<=1) for(int i=0;i<n;i+=mid<<1) for(int j=0;j<mid;j++) a[i+j]|=a[i+j+mid]; return; } signed main() { read(n); maxn=((1<<18)-1); tmp=maxn; for(int i=1;i<=n;i++) read(a[i]),tmp&=a[i]; ll ans=tmp*n; for(int i=1;i<=n;i++) a[i]-=tmp,b[(~a[i])&maxn]=1; fwt(b,maxn); for(int i=1;i<=maxn;i++) { f[i]=inf; for(int s=i;s;s=(s-1)&i) { if(b[s]) f[i]=min(f[i],f[i^s]+(i^s));//坑:代价是i^s 不是s } } printf("%lld\n",f[maxn]+ans); exit(0); }
posted @   CelticOIer  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示