GFOJ problem64 新年组队 解题报告

题目:http://www.gdfzoj.com/oj/problem/64

描述:给一个长度为n的数组a[i],和m个区间<l,r>,求<l,r>内最小子区间<i,j>使a[i] == a[j](n,m<5*10^5)

一开始想到 离散化+RMQ,但后来发现RMQ不能像维护区间最值一样用max/min维护该问题

正解是主席树或分块,时限5s,但分块写好的话1s也能过(746ms水过),其他题解写在注释里了

关于块的大小的推理可能不是很严谨,具体还要看不同题目的数据范围.

下面放代码

#include <cstdio>
#include <map>
#define k(x) ((x-1) / K+1)
//x属于分块中的第k(x)块
using namespace std;

const int N = 500010,K = 150,M = N / K + 10;
//K为块的大小 当K=1000时耗时1620ms,K=150时耗时746ms(相信玄学)
//M为块数
int a[N],f[M][M],nxt[N],pre[N],n,m,i,j,l,r,ans; 
//f为分块数组  f[i][j]表示第i块到第j块的最小方案
//想不到只用next数组的方法,于是nxt[i]表示 a[i]=a[nxt[i]] 且 nxt[i] > i 的最小值
//pre[i]表示 a[pre[i]]=a[i] 且 pre[i] < i 的最大值
map <int,int> q;
//偷懒用 map 离散化

inline int min(int a,int b) {
	if (!a) return b;
	if (!b) return a;
	return (a < b) ? a : b;
} //求a,b的最小值,特判a==0 b==0

int main() {
	scanf("%d%d",&n,&m);
	for (i=1;i<=n;i++) {
		scanf("%d",a+i);
		j = q[a[i]];
		if (j) {
			nxt[j] = i;  pre[i] = j;
			f[k(j)][k(i)] = min(f[k(j)][k(i)],i-j);
		} q[a[i]] = i;
	} //一个简单的用 map 实现的离散化 复杂度约 O(n log n)
	
	for (i=1;i<n/K+1;i++) for (j=1;i+j<n/K+1;j++) {
		f[i][i+j] = min(f[i][i+j-1],f[i][i+j]);
		f[i][i+j] = min(f[i][i+j],f[i+1][i+j]);
	} //i为区间长度,j为起始位置,一个预处理
	//f[i][i+j] = min(f[i][i+j],f[i+1][i+j],f[i][i+j+i])
	//复杂度 O(n²/k²)
	
	while (m--) {
		scanf("%d%d",&l,&r);
		l ^= ans;  r ^= ans;
		ans = f[k(l)+1][k(r)-1];
		//块内最小答案 O(1)
		
		for (i=l;i<k(l) * K && i<=r;i++)
		if (nxt[i] <= r && nxt[i]) ans = min(ans,nxt[i] - i);
		l = i - 1;
		
		for (i=r;i>(k(r)-1) * K && i>=l;i--)
		if (pre[i] >= l) ans = min(ans,i - pre[i]);
		//暴力枚举块外答案(pre和nxt的作用) 复杂度 O(2k)
		
		if (!ans) ans = -1;
		printf("%d\n",ans);
	}
	//综上,时间复杂度约 O(n log n + n²/k² + 2mk)
	//代入n=m=500000,k≈540。但n,m<500000,因此k取约150-400是有一定道理的。
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值