P13552 鱼类考古学

来水题解了喵。这题有紫?

首先合理猜测最终的操作一定是 \(n-k\)\(1\) 操作后若干次 \(2\) 操作,不妨认为这是对的考虑怎么证明一下。转化一下,我们要证的就是:\((x \otimes y) + z \geq (x + y) \otimes z\)(话说为什么按位与符号是这个,好奇怪喵)。容易发现 \((x + y) \otimes z \leq z\),而 \((x \otimes y) + z \geq z\)

接着我们考虑贪心的进行前 \(n-k\)\(1\) 操作。我们发现 \(a \otimes b = a + b - a | b\) 其中 \(|\) 为按位或操作。假设我们每步操作的两个数的按位或之和为 \(b\),那么我们最终的答案即为 \(\sum a_i - b\),也就是说我们要最小化 \(b\)

于是我们发现:对于二进制下从低到高第 \(i\) 位为 \(0\) 的数和第 \(i\) 位为 \(1\) 的数(不妨假设更高位均相同),优先选择第 \(i\) 位为 \(0\) 的数进行操作是更优的(因为前者操作后的产生的按位或第 \(i\) 位为 \(0\) 而后者为 \(1\))。于是我们有了一个贪心策略:从大到小枚举 \(2^i\),优先将这一位为 \(0\) 的数先操作,若不够操作数则继续向 \(1\) 操作。

考虑如何维护这个东西。很显然的想到 01-Trie,建出树后从根向下遍历保证枚举的 \(2^i\) 从大到小。若这一位为 \(0\) 的数足够 \(k\) 次操作,就向 \(0\) 儿子继续遍历;否则需要对 \(1\) 儿子进行操作。设 \(0\) 儿子子树中所有数的按位与结果为 \(x\),那么我们要将这个数添加到我们进行操作的序列中去。如果每次记录这样的 \(x\) 未免太过麻烦,我们令 \(x\) 的第 \(i\) 位为 \(1\) 将其加入 Trie 树中,这样就可以优雅地继续向 \(1\) 儿子递归,统计答案时减去 \(2^i\) 即可。

考虑如何获得答案。在递归过程中,如果递归到叶子节点且还剩余 \(k\) 次操作与 \(m\) 个数,那么答案就加上 \((m - k) \times val\)\(val\) 为当前遍历到的数);若向 \(0\) 儿子遍历,那么就可以取遍 \(1\) 儿子子树中的数之和加入答案。可以感性理解一下这为什么是对的。

回顾整道题,我们只需要在 Trie 树上维护子树中数的个数、子树中数的和与子树中数的按位与即可(注意一个细节是,当遍历到的点只有 \(0\) 儿子或者 \(1\) 儿子,直接递归即可,否则样例第三组数据可能输出 \(0\)),很简单很好写喵。时间复杂度为 \(\mathcal{O}(n \log a_i)\)(处理出 Trie 树),递归求解是 \(\mathcal{O}(\log a_i)\) 的。

const int N=1e6+5,S=2e7+5;
int T,n,k,a[N];
int tr[S][2],siz[S],nd[S],cnt;
ll ans,sum[S];

inline int new_node() {
	++cnt,siz[cnt]=0,nd[cnt]=(1<<30)-1;
	sum[cnt]=tr[cnt][0]=tr[cnt][1]=0;
	return cnt;
}
inline void insert(int x) {
	int p=0; siz[0]++;
	for(int j=29;j>=0;j--) {
		int dir=(x>>j)&1;
		if(!tr[p][dir]) tr[p][dir]=new_node();
		siz[p=tr[p][dir]]++,nd[p]&=x,sum[p]+=x;
	}
	return ;
}
#define lc tr[p][0]
#define rc tr[p][1]
inline void solve(int p,int k,int bit,int num) {
	// printf("solve(%d,%d,%d,%d): %lld\n",p,k,bit,num,ans);
	if(!bit) {
		ans+=(ll)num*(siz[p]-k);
		// printf("%d %d %d %lld\n",num,siz[p],k,ans);
		return ;
	}
	if(!lc) return solve(rc,k,bit-1,num|(1<<bit-1));
	if(!rc) return solve(lc,k,bit-1,num);
	if(lc&&siz[lc]>k) {
		ans+=sum[rc];
		solve(lc,k,bit-1,num);
	}
	else {
		if(lc) insert(nd[lc]|(1<<bit-1));
		solve(rc,k-(lc?siz[lc]-1:0),bit-1,num|(1<<bit-1));
		ans-=(1<<bit-1);
	}
	return ;
}

int main() {
	read(T);
	while(T--) {
		read(n,k),k=n-k,cnt=tr[0][0]=tr[0][1]=0;
		for(int i=1;i<=n;i++) read(a[i]),insert(a[i]);
		ans=0,solve(0,k,30,0);
		write(ans),_E;
	}
	return 0;
}
posted @ 2025-08-02 20:59  LinkCatTree  阅读(101)  评论(3)    收藏  举报