BZOJ5495:[2019省队联测]异或粽子 (Trie+贪心)

题目传送门

BZOJ(没题面)
洛谷(有题面)


题目大意

给出一个长为n的序列,选择k个不完全重合的区间使得每个区间的异或值的总和最大。


题解

作为高三退役狗康复训练的第一题,我发现我的做法有点特立独行……

4月份Ghastlcon跟我说这题的时候,我在想为什么要用可持久化Trie。而现在我YY出了一个既不用可持久化也不用堆的方法。

首先把所有的异或前缀和扔进Trie,接下来目标就是找最大的k对异或值。

接下来考虑根节点的两个儿子的size(即endpos的个数),记为m和n。
一:如果 m ∗ n m*n mn小于等于k,那么这 m ∗ n m*n mn对是一定要取的,暴力贡献答案后令 k − = m ∗ n k-=m*n k=mn。剩下的那些对分别从根的两棵子树取。

二:如果 m ∗ n m*n mn大于k,那么k对的最高位必定都为1,先贡献进答案再说。然而有一个限制,就是剩下的位里只能一棵在根的0子树,另一棵在1子树。

这个限制不好搞,继续分类讨论。第二种情况中,我们接下来要看根的0子树的1子树的size乘根的1子树的0子树的size,再加根的0子树的0子树的size乘根的1子树的1子树的size和k的关系。有点绕口,不妨简记为 s ( 01 ) ∗ s ( 10 ) + s ( 00 ) ∗ s ( 11 ) s(01)*s(10)+s(00)*s(11) s(01)s(10)+s(00)s(11)

如果它小于等于k,仿照一的做法,我们暴力计算这些对,剩下的对就要从00,10和01,11中取。如果它大于k,那么答案的k对里这一位全为1,限制增强为剩下的位要从01,10或00,11中取。

现在我们试图总结出一个稍微程序化的过程,它本质上有点像trie上的双路DP。只不过因为我们按层依次剖分了这颗trie,所以当前构造的两条路径的端点一定位于同一层。而且在我们处理某一层时,这一层有很多个“双路端点对”,用一个数组记录下来即可。

具体实现时,就是把上一层的端点对拿出来,看一下他们的0,1子树size分别对应相乘之和与当前k的关系(注意两个端点重合的情况以及k可能不断变小)。如果小于等于k,暴力取出这些对贡献答案,然后剩下的对在端点对的0,0或1,1子树中找。如果大于k,则限制增强为对应端点对的0,1或1,0子树中找。

显然trie上的一个点只会被处理一次,时间复杂度 O ( ( n + k ) ∗ l o g ( m a x v a l ) ) O((n+k)*log(maxval)) O((n+k)log(maxval))。后来看到别人都用了更加简单粗暴的方法(自己找来看吧,可持久化+堆)。不过实际运行时间上这种方法貌似更好:

(无聊看了看status发现自己居然rk1!)

code

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=500100;
const int maxl=33;
typedef unsigned int UI;
typedef long long LL;

struct Tnode
{
	Tnode *son[2];
	int id,Size;
} tree[maxn*maxl];
Tnode *Root;
int cur=-1;

UI f[maxn];
UI a[maxn];

Tnode *p[maxn];
Tnode *q[maxn];
int num=1;

Tnode *tp[maxn];
Tnode *tq[maxn];
int temp;

UI A[maxn];
UI B[maxn];
int nA,nB;

LL sum,ans=0;
int n,k;

Tnode *New_node()
{
	cur++;
	tree[cur].son[0]=tree[cur].son[1]=NULL;
	tree[cur].id=-1;
	tree[cur].Size=0;
	return tree+cur;
}

void Insert(Tnode *P,UI val,int w,int Id)
{
	P->Size++;
	if (w<0)
	{
		P->id=Id;
		return;
	}
	
	UI v=1;
	v=(v<< ((unsigned int)w) );
	UI d=(val&v);
	if (d>0) d=1;
	
	if (!P->son[d]) P->son[d]=New_node();
	Insert(P->son[d],val,w-1,Id);
}

LL Get(Tnode *P)
{
	if (!P) return 0;
	return P->Size;
}

void Dfs(Tnode *P,UI *C,int &v)
{
	if (!P) return;
	if (P->id>=0)
		for (int i=1; i<=P->Size; i++) C[++v]=f[P->id];
	Dfs(P->son[0],C,v);
	Dfs(P->son[1],C,v);
}

void Calc(Tnode *P,Tnode *Q,int w)
{
	if ( !P || !Q ) return;
	nA=nB=0;
	Dfs(P,A,nA);
	Dfs(Q,B,nB);
	
	LL v=1;
	w++;
	v=(v<< ((long long)w) );
	v--;
	for (int i=1; i<=nA; i++) A[i]=(A[i]&v);
	for (int j=1; j<=nB; j++) B[j]=(B[j]&v);
	
	for (int i=1; i<=nA; i++)
		for (int j=1; j<=nB; j++) ans+=( A[i]^B[j] );
}

void Push(Tnode *P,Tnode *Q)
{
	if ( !P || !Q ) return;
	temp++;
	tp[temp]=P;
	tq[temp]=Q;
}

void Work(int w)
{
	if ( w<0 || k<=0 || num<=0 ) return;
	
	sum=0;
	for (int i=1; i<=num; i++)
		if (p[i]==q[i]) sum+=( Get(p[i]->son[0])*Get(p[i]->son[1]) );
		else
		{
			sum+=( Get(p[i]->son[0])*Get(q[i]->son[1]) );
			sum+=( Get(q[i]->son[0])*Get(p[i]->son[1]) );
		}
	
	temp=0;
	if (sum<=k)
	{
		k-=sum;
		for (int i=1; i<=num; i++)
		{
			if (p[i]==q[i]) Calc(p[i]->son[0],p[i]->son[1],w);
			else
			{
				Calc(p[i]->son[0],q[i]->son[1],w);
				Calc(q[i]->son[0],p[i]->son[1],w);
			}
			Push(p[i]->son[0],q[i]->son[0]);
			Push(p[i]->son[1],q[i]->son[1]);
		}
	}
	else
	{
		ans+=( ((long long)k)<<w );
		for (int i=1; i<=num; i++)
			if (p[i]==q[i]) Push(p[i]->son[0],p[i]->son[1]);
			else
			{
				Push(p[i]->son[0],q[i]->son[1]);
				Push(q[i]->son[0],p[i]->son[1]);
			}
	}
	
	num=temp;
	for (int i=1; i<=num; i++) p[i]=tp[i],q[i]=tq[i];
	Work(w-1);
}

int main()
{
	scanf("%d%d",&n,&k);
	f[0]=0;
	for (int i=1; i<=n; i++) scanf("%u",&a[i]),f[i]=(f[i-1]^a[i]);
	
	Root=New_node();
	for (int i=0; i<=n; i++) Insert(Root,f[i],31,i);
	
	p[1]=q[1]=Root;
	Work(31);
	
	printf("%lld\n",ans);
	
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值