题解 【UR #17】滑稽树上滑稽果
本来还以为每次取最优的是对的,结果题解里说部分分给了这个做法那一定是错的。。
考虑到这个题,如果我们算出所有数的按位 \(\operatorname{and}\) 和,设他为 \(x\) ,那么答案每一个数一定包含 \(x\),于是我们可以先把答案加上 \(n\times x\),再把每个数减掉 \(x\) ,那么所有数的按位 \(\operatorname{and}\) 和酒变成 \(0\) 了。
再来考虑怎样最优,我们发现一旦变成 \(0\),后面的答案就全部都是 \(0\),所以其实就是在让我们求最小的一个集合,他的所有元素按位 \(and\) 和为 \(0\) 。
考虑 dp,设 \(f_S\) 让 \(S\) 集合中的位变成 \(0\),其他位不管的最小代价,那么答案就是 \(f_{maxn}\)。
考虑转移,我们每次枚举一个数加入这个集合, \(f_{S}=min(f_{S},f_{S\&a_i}+(S\&a_i))\)。
这样虽然会加入重复的数,但是一定不优,不用考虑。
于是得到一个 \(O(n^2)\) 的算法。
考虑优化,我们每次枚举 \(S\) 的子集 \(T\) ,强制让 \(T\) 变为 \(0\) 。
那么问题就转化为判断有没有数 \(T\) 集合全是 \(0\),可以 fwt 来解决。
复杂度就变成了 \(O(3^{\log_2^n})=O(n^{\log_2^3})\),可以通过。
\(\sf{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);
}