天天看点

bzoj 4241: 历史研究 分块

       首先分成N^0.5块,然后答案显然是中间一整段的最大值,或者是两端零星的部分。

       那么可以得到f[i][j]表示第i块到第j块的答案;然后就需要快速求出零星部分出现的次数,用一个前缀和s[i][j]表示在前i块中j出现的次数。

       离散化搞一搞。

       好像莫队也是可以的把。

AC代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define ll long long
#define N 100005
#define M 335
using namespace std;

int n,m,cnt,c[N],num[N],g[N],s[M][N],l[M],r[M],blg[N];
ll f[M][M]; struct node{ int x,y; }a[N];
int read(){
	int x=0; char ch=getchar();
	while (ch<'0' || ch>'9') ch=getchar();
	while (ch>='0' && ch<='9'){ x=x*10+ch-'0'; ch=getchar(); }
	return x;
}
bool cmp(node u,node v){ return u.x<v.x; }
int main(){
	n=read(); int i,j,k,cas=read();
	for (i=1; i<=n; i++){
		a[i].x=read(); a[i].y=i;
	}
	sort(a+1,a+n+1,cmp);
	for (i=1; i<=n; i++){
		if (i==1 || a[i].x!=a[i-1].x) num[++cnt]=a[i].x;
		c[a[i].y]=cnt;
	}
	m=(int)sqrt(n);
	for (i=1; i<=m; i++){
		l[i]=r[i-1]+1; r[i]=r[i-1]+m;
	}
	r[m]=n;
	for (i=1; i<=m; i++)
		for (j=l[i]; j<=r[i]; j++) blg[j]=i;
	for (i=1; i<=m; i++){
		memcpy(s[i],s[i-1],sizeof(s[i]));
		for (j=l[i]; j<=r[i]; j++) s[i][c[j]]++;
	}
	for (i=1; i<=m; i++){
		for (j=i; j<=m; j++){
			f[i][j]=f[i][j-1];
			for (k=l[j]; k<=r[j]; k++){
				g[c[k]]++; f[i][j]=max(f[i][j],(ll)g[c[k]]*num[c[k]]);
			}
		}
		for (j=1; j<=cnt; j++) g[j]=0;
	}
	int x,y,u,v; ll ans;
	while (cas--){
		x=read(); y=read(); u=blg[x]; v=blg[y];
		if (u+1<v){
			ans=f[u+1][v-1];
			for (i=x; i<=r[u]; i++) g[c[i]]++;
			for (i=y; i>=l[v]; i--) g[c[i]]++;
			for (i=x; i<=r[u]; i++) ans=max(ans,(ll)(g[c[i]]+s[v-1][c[i]]-s[u][c[i]])*num[c[i]]);
			for (i=y; i>=l[v]; i--) ans=max(ans,(ll)(g[c[i]]+s[v-1][c[i]]-s[u][c[i]])*num[c[i]]);
			for (i=x; i<=r[u]; i++) g[c[i]]=0;
			for (i=y; i>=l[v]; i--) g[c[i]]=0;
		} else{
			ans=0;
			for (i=x; i<=y; i++){
				g[c[i]]++; ans=max(ans,(ll)g[c[i]]*num[c[i]]);
			}
			for (i=x; i<=y; i++) g[c[i]]=0;
		}
		printf("%lld\n",ans);
	}
	return 0;
}
           

by lych

2016.3.25