异或序列 [set优化DP]

也许更好的阅读体验

D e s c r i p t i o n \mathcal{Description} Description
有一个长度为 n n n的自然数序列 a a a,要求将这个序列分成至少 m m m 个连续子段
每个子段的价值为该子段的所有数的按位异或
要使所有子段的价值按位与的结果最大,输出这个最大值

T T T组询问
T ≤ 10 , n , m ≤ 1000 , a i ≤ 2 30 T\leq 10,n,m\leq 1000,a_i\leq 2^{30} T10,n,m1000,ai230
S o l u t i o n \mathcal{Solution} Solution
实际上数据范围可开大很多

我们贪心的一位一位的确定最终答案,即看当前考虑的位能否为 1 1 1
s i s_i si表示前 i i i个数的异或和, ⨁ \bigoplus 表示异或
设当前考虑到了第 b b b
r e s = a n s ∣ ( 1 < < b ) res=ans|(1<<b) res=ans(1<<b)
一段区间 [ j + 1 , i ] [j+1,i] [j+1,i]如果是一个合法的区间,可以得到
( s i ⨁ s j ) & r e s = r e s \left(s_i\bigoplus s_j\right)\&res=res (sisj)&res=res
于是我们得到了一个 n 2 l o g n^2log n2log D P DP DP方程
f i = m a x f i , f j + 1 f_i=max{f_i,f_j+1} fi=maxfi,fj+1其中 ( s i ⨁ s j ) = r e s \left(s_i\bigoplus s_j\right)=res (sisj)=res
枚举位是 l o g log log的,这样就可以 A C AC AC此题了

实际这个 D P DP DP可以进一步优化
( s i ⨁ s j ) & r e s = r e s \left(s_i\bigoplus s_j\right)\&res=res (sisj)&res=res可以推出
( s i & r e s ) ⨁ ( s j & r e s ) = r e s \left(s_i \& res\right)\bigoplus \left(s_j\& res\right)=res (si&res)(sj&res)=res
⇒ s i & r e s = ( s j & r e s ) ⨁ r e s \Rightarrow s_i \& res=\left(s_j\& res\right)\bigoplus res si&res=(sj&res)res
即要将 s i s_i si s j s_j sj这段作为一个子段必须满足上面的条件
因为题目是至少 m m m段,所以分的越多越好
则我们可以考虑完 s i s_i si的最优答案后将 s i ⨁ r e s s_i\bigoplus res sires作为第一关键字存进 s e t set set
f i = f i n d ( s i ⨁ r e s ) f_i=find(s_i\bigoplus res) fi=find(sires)
这样一次转移就是 l o g log log
复杂度为 n l o g 2 nlog^2 nlog2

C o d e \mathcal{Code} Code

/*******************************
Author:Morning_Glory
LANG:C++
Created Time:2019年10月26日 星期六 09时18分19秒
*******************************/
#include <cstdio>
#include <fstream>
#include <cstring>
#include <set>
#define mp make_pair
using namespace std;
const int maxn = 2003;
//{{{cin
struct IO{
	template<typename T>
	IO & operator>>(T&res){
		res=0;
		bool flag=false;
		char ch;
		while((ch=getchar())>'9'||ch<'0')	flag|=ch=='-';
		while(ch>='0'&&ch<='9')	res=(res<<1)+(res<<3)+(ch^'0'),ch=getchar();
		if (flag)	res=~res+1;
		return *this;
	}
}cin;
//}}}
int n,m,T,ans;
int a[maxn],s[maxn];
set < pair<int,int> > v;
set < pair<int,int> > :: iterator it,nx;
//{{{solve
void solve (int x)
{
	int res=ans|(1<<x);
	bool flag;
	v.clear();
	for (int i=1;i<=n;++i){
		int val=s[i]&res;
		v.insert(mp(val,0));
		nx=it=v.lower_bound(mp(val,0));
		++nx;
		while (nx!=v.end()&&nx->first==val){
			v.erase(it);
			it=nx,++nx;
		}
		if (it->second==0){
			if (val==res){
				v.insert(mp(val^res,1));
				if (i==n)	flag=it->second+1>=m;
			}
		}
		else{
			v.insert(mp(val^res,(it->second)+1));
			if (i==n)	flag=it->second+1>=m;
		}
	}
	if (flag)	ans=res;
}
//}}}
int main()
{
	cin>>T;
	while (T--){
		cin>>n>>m;
		ans=0;
		for (int i=1;i<=n;++i){
			cin>>a[i];
			s[i]=s[i-1]^a[i];
		}

		for (int i=29;~i;--i)	solve(i);
		printf("%d\n",ans);
	}
	return 0;
}

如有哪里讲得不是很明白或是有错误,欢迎指正
如您喜欢的话不妨点个赞收藏一下吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值