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

link

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

考虑到这个题,如果我们算出所有数的按位 \(\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);
}
posted @ 2022-05-05 10:56  CelticOIer  阅读(16)  评论(0编辑  收藏  举报