题解 【UR #17】滑稽树上滑稽果
本来还以为每次取最优的是对的,结果题解里说部分分给了这个做法那一定是错的。。
考虑到这个题,如果我们算出所有数的按位 and 和,设他为 x ,那么答案每一个数一定包含 x,于是我们可以先把答案加上 n×x,再把每个数减掉 x ,那么所有数的按位 and 和酒变成 0 了。
再来考虑怎样最优,我们发现一旦变成 0,后面的答案就全部都是 0,所以其实就是在让我们求最小的一个集合,他的所有元素按位 and 和为 0 。
考虑 dp,设 fS 让 S 集合中的位变成 0,其他位不管的最小代价,那么答案就是 fmaxn。
考虑转移,我们每次枚举一个数加入这个集合, fS=min(fS,fS&ai+(S&ai))。
这样虽然会加入重复的数,但是一定不优,不用考虑。
于是得到一个 O(n2) 的算法。
考虑优化,我们每次枚举 S 的子集 T ,强制让 T 变为 0 。
那么问题就转化为判断有没有数 T 集合全是 0,可以 fwt 来解决。
复杂度就变成了 O(3logn2)=O(nlog32),可以通过。
Code
#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);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话