異或序列 [set優化DP]

也許更好的閱讀體驗

Description\mathcal{Description}
有一個長度爲 nn的自然數序列 aa,要求將這個序列分成至少 mm 個連續子段
每個子段的價值爲該子段的所有數的按位異或
要使所有子段的價值按位與的結果最大,輸出這個最大值

TT組詢問
T10,n,m1000,ai230T\leq 10,n,m\leq 1000,a_i\leq 2^{30}
Solution\mathcal{Solution}
實際上數據範圍可開大很多

我們貪心的一位一位的確定最終答案,即看當前考慮的位能否爲11
sis_i表示前ii個數的異或和,\bigoplus表示異或
設當前考慮到了第bb
res=ans(1<<b)res=ans|(1<<b)
一段區間[j+1,i][j+1,i]如果是一個合法的區間,可以得到
(sisj)&res=res\left(s_i\bigoplus s_j\right)\&res=res
於是我們得到了一個n2logn^2logDPDP方程
fi=maxfi,fj+1f_i=max{f_i,f_j+1}其中(sisj)=res\left(s_i\bigoplus s_j\right)=res
枚舉位是loglog的,這樣就可以ACAC此題了

實際這個DPDP可以進一步優化
(sisj)&res=res\left(s_i\bigoplus s_j\right)\&res=res可以推出
(si&res)(sj&res)=res\left(s_i \& res\right)\bigoplus \left(s_j\& res\right)=res
si&res=(sj&res)res\Rightarrow s_i \& res=\left(s_j\& res\right)\bigoplus res
即要將sis_isjs_j這段作爲一個子段必須滿足上面的條件
因爲題目是至少mm段,所以分的越多越好
則我們可以考慮完sis_i的最優答案後將siress_i\bigoplus res作爲第一關鍵字存進setset
fi=find(sires)f_i=find(s_i\bigoplus res)
這樣一次轉移就是loglog
複雜度爲nlog2nlog^2

Code\mathcal{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;
}

如有哪裏講得不是很明白或是有錯誤,歡迎指正
如您喜歡的話不妨點個贊收藏一下吧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章