MisakaMKT

「JOISC 2014 Day1」历史研究 --- 回滚莫队

题目又臭又长,但其实题意很简单。

给出一个长度为N的序列与Q个询问,每个询问都对应原序列中的一个区间。对于每个查询的区间,设数Xi在此区间出现的次数为SumXi,我们需要求出对于当前区间XiSumXi的最大值。

数据范围:1N,Q105,1XI109


众所周知,对于没有修改的区间查询问题且数据范围在1e5的题目,我们首先就可以考虑使用莫队来解决,事实上这道题也是可以用莫队来解决的,不过需要一点变形。

对每个询问进行分块排序后,使用莫队算法。将原序列的数进行离散化,记每个数出现的次数为Cntx,当前莫队的左右下标为lr,当前最优答案为sum

我们发现当l减少和r增加时的情况很好更新答案(也就是原莫队的add操作,与之对应的删除操作也就是del),只需要Cntr++,然后求一个最大值max(sum,Cntr)

但是del操作就很难实现。如果删除的数对应的是最大值,也就是在sum=CntrXr中,Xr了,那么我们就不能保证当前的sum是最大的。考场上笔者想到的解决方法是维护一个次大值,但可以发现如果要维护次大值那还要维护一个第3大值。。。

考场上笔者是使用的线段树来解决这个问题,维护每个CntiXi的最大值,时间复杂度对应的是O(nnlogn)。尽管理论上可以过,卡常之后也确实可以过,但并不完美因为我不会卡常。这里是常数比较大的笔者的40分套线段树代码。

#include <iostream>
#include <cstdio>
#include <map>
#include <algorithm>
#include <cmath>
using namespace std;

#define LL long long
const int N=100020;

struct node {
	LL p,sum;
	node() {};
	node(LL S,LL P) { p=P; sum=S; }
}; 

LL ans[N],maxx[N<<4],M2[N],n,m,A[N],T,cnt;
map<LL,LL> M1;

struct Qu {
	int l,r,p;
	bool operator < (const Qu &nxt) {//分块排序
		if(l/T+1 != nxt.l/T+1) return l/T+1<nxt.l/T+1;
		else return r<nxt.r;
	}
}tzy[N];

void insert(int i,int l,int r,int x,int t) {//线段树维护
	if(l==r) {
		maxx[i]+=t;
		return ;
	}
	int mid=(l+r)>>1;
	if(x<=mid) insert(i<<1,l,mid,x,t);
	else insert(i<<1|1,mid+1,r,x,t);
	maxx[i]=max(maxx[i<<1],maxx[i<<1|1]);
}

void add(int now) {
	insert(1,1,cnt,A[now],M2[A[now]]);
}

void del(int now) {
	insert(1,1,cnt,A[now],-M2[A[now]]);
}

int main() {
	cin>>n>>m; T=sqrt(n);
	for(int i=1;i<=n;i++) {
		scanf("%lld",&A[i]);	
		if(!M1[A[i]]) {
			M1[A[i]]=++cnt;	
			M2[cnt]=A[i];
		}
		A[i]=M1[A[i]];
	}
//	cout<<endl<<cnt<<endl;
	for(int i=1;i<=m;i++) {
		scanf("%d%d",&tzy[i].l,&tzy[i].r);
		tzy[i].p=i;
	}
	sort(tzy+1,tzy+1+m);
	int l=1,r=1; insert(1,1,cnt,A[1],M2[A[1]]);
//	for(int i=1;i<=m;i++) { cout<<tzy[i].l<<' '<<tzy[i].r<<endl;}
	for(int i=1;i<=m;i++) {
		while(r<tzy[i].r) add(++r);
		while(l>tzy[i].l) add(--l);
		while(r>tzy[i].r) del(r--);
		while(l<tzy[i].l) del(l++);
		ans[tzy[i].p]=maxx[1];
	}
	for(int i=1;i<=m;i++) cout<<ans[i]<<endl;
}

另一种思路其实是值得我们学习的。如果del操作不好实现,那为什么我们不能跳出优化del操作的框框,而是想办法将问题转化为只使用add操作呢?

于是便有了莫队算法的变形:回滚莫队,适用于不好维护del操作的情况,且它的时间复杂度也十分优美O(nn)(不像上一个又臭又长)。

回滚莫队可以说是将分块的思想用到了极致。我们将问题一个块一个块的处理。因为我们是将询问的r从小到大排序,所以对于同一块的处理,可以保证r的转移是O(n)的,且只涉及到add操作。

但是l就不好处理了,因为它的排列相较于r的排列是无序的。但因为是分块排列,我们可以保证从当前块的右端点Ri,到l,最多只有n步。

于是我们便可以考虑这样一种策略。每次处理都将l移到Ri,这样可以保证lr都只有add操作。当前询问处理完后,只需要将l“滚”回来。因为l的转移最多为n次,r转移最多为n次,所以复杂度为O(nn)

思路讲完了,但还有种特殊情况需要处理,当Lil,rRi时,也就是当前询问左右端点都处于同一个块时运用上述方法并不好处理。因为可以保证rln,所以只需要暴力跑一边即可。

细节比较多,详情见代码。

#include <iostream>
#include <cstdio>
#include <map>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;

#define LL long long
const int N=100020;

LL ans[N],B[N],q,cnt[N],block[N],L[N],R[N],Cntl,n,sum,m,p,A[N],T;

struct Qu {
	int l,r,p;
	bool operator < (const Qu &nxt) {
		return block[l]==block[nxt.l] ? r<nxt.r:l<nxt.l;
	}
}tzy[N];

void divide() {
	for(int i=1;i<=q;i++) L[i]=R[i-1]+1,R[i]=L[i]+T-1;
	if(R[q]<n) L[q+1]=R[q]+1,R[q+1]=n,q++;
	for(int i=1;i<=n;i++) block[i]=(i-1)/T+1;
}

void add(int x) {
	cnt[A[x]]++;
	if(cnt[A[x]]*B[A[x]]>sum) sum=cnt[A[x]]*B[A[x]];
} 

LL solve(int l,int r) {
	static int c[N]; 
	LL solu=0;
	for(int i=l;i<=r;i++) ++c[A[i]];
	for(int i=l;i<=r;i++) solu=max(solu,c[A[i]]*B[A[i]]);
	for(int i=l;i<=r;i++) --c[A[i]];
	return solu;
}

int main() {
	cin>>n>>m; T=sqrt(n); q=n/T;
	for(int i=1;i<=n;i++) scanf("%lld",&A[i]),B[i]=A[i];
	for(int i=1;i<=m;i++) scanf("%d%d",&tzy[i].l,&tzy[i].r),tzy[i].p=i;
	sort(B+1,B+1+n);
	p=unique(B+1,B+1+n)-B-1;
//	cout<<endl<<cnt<<endl;
	for(int i=1;i<=n;i++) A[i]=lower_bound(B+1,B+1+p,A[i])-B;
	divide();
	sort(tzy+1,tzy+1+m);
	
	for(int i=1,l,r,j=1;i<=q;i++) {
		r=R[i]; sum=0;
		memset(cnt,0,sizeof(cnt));
		while(block[tzy[j].l]==i) {
			l=R[i]+1;
			if(tzy[j].r-tzy[j].l<=T)
				ans[tzy[j].p]=solve(tzy[j].l,tzy[j].r),++j;
			else {
				while(r<tzy[j].r) add(++r);
				LL tmp=sum;
				while(l>tzy[j].l) add(--l);
				ans[tzy[j].p]=sum;
				sum=tmp;
				while(l<=R[i]) --cnt[A[l++]];
				++j;
			}
		}
	}
	for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
}

考试还有一个每个节点为44的矩阵的题没有写。对于我这种一个月一更的作者,写个博客要一上午的人。。。还早呢

posted on   MisakaMKT  阅读(208)  评论(0编辑  收藏  举报

编辑推荐:
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
阅读排行:
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 上周热点回顾(2.17-2.23)

导航

统计信息

点击右上角即可分享
微信分享提示