[CSAcademy]Or Problem

[CSAcademy]Or Problem

题目大意:

一个长度为\(n(n\le2\times10^5)\)的序列\(A(0\le A_i<2^{20})\),将其分为恰好\(m\)个连续段,设每一段的代价为这一段数字的或,总代价为每一段代价和。求最小代价和。

思路:

一个普通的DP思路是,对于每个数\(A_i\),枚举每一位,找到上一个在这一位上为\(1\)的数\(A_k\)\(A_{k+1\sim i}\)为最后一段。转移方程为\(f[i][j]=\max\{f[k][j-1]+\vee_{\ell=k+1}^i A_{\ell}\}\)

使用带权二分可以去掉\(m\)段的状态。

源代码:

#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
inline int getint() {
	register char ch;
	while(!isdigit(ch=getchar()));
	register int x=ch^'0';
	while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0');
	return x;
}
typedef long long int64;
const int N=2e5+1,logN=18,logA=20;
int n,st[N][logN],p[logA],g[N];
int64 f[N];
inline int lg2(const float &x) {
	return ((unsigned&)x>>23&255)-127;
}
inline int calc(const int &l,const int &r) {
	const int k=lg2(r-l+1);
	return st[l][k]|st[r-(1<<k)+1][k];
}
inline void solve(const int &c) {
	memset(p,0,sizeof p);
	for(register int i=1;i<=n;i++) {
		f[i]=calc(1,i)-c;
		g[i]=0;
		for(register int j=0;j<logA;j++) {
			if(st[i][0]>>j&1) p[j]=i;
			if(!p[j]) continue;
			const int64 tmp=f[p[j]-1]+calc(p[j],i)-c;
			if(f[i]<tmp) {
				f[i]=tmp;
				g[i]=0;
			}
			if(tmp==f[i]) g[i]=std::max(g[i],g[p[j]-1]+1);
		}
	}
}
int main() {
	n=getint();
	const int m=getint();
	for(register int i=1;i<=n;i++) st[i][0]=getint();
	for(register int j=1;j<logN;j++) {
		for(register int i=1;i+(1<<(j-1))<=n;i++) {
			st[i][j]=st[i][j-1]|st[i+(1<<(j-1))][j-1];
		}
	}
	int l=0,r=1e9;
	int64 ans=0;
	while(l<=r) {
		const int mid=(l+r)>>1;
		solve(mid);
		if(g[n]>=m) {
			l=mid+1;
			ans=f[n]+1ll*m*mid;
		} else {
			r=mid-1;
		}
	}
	printf("%lld\n",ans);
	return 0;
}

posted @ 2018-12-19 15:53  skylee03  阅读(1294)  评论(0编辑  收藏  举报