区间分块模板

本次主要把区间分块模板贴一下子

例题:

问题 J: 蒲公英

时间限制: 1.000 Sec  内存限制: 256 MB
提交 状态

题目描述

亲爱的哥哥: 
你在那个城市里面过得好吗? 
我在家里面最近很开心呢。昨天晚上奶奶给我讲了那个叫「绝望」的大坏蛋的故事的说!
它把人们的房子和田地搞坏,还有好多小朋友也被它杀掉了。我觉得把那么可怕的怪物召唤
出来的那个坏蛋也很坏呢。不过奶奶说他是很难受的时候才做出这样的事的…… 
最近村子里长出了一大片一大片的蒲公英。一刮风,这些蒲公英就能飘到好远的地方了
呢。我觉得要是它们能飘到那个城市里面,让哥哥看看就好了呢!  
哥哥你要快点回来哦! 

                                                                           爱你的妹妹 Violet 

Azure 读完这封信之后微笑了一下。 
“蒲公英吗……” 
在乡下的小路旁种着许多蒲公英,而我们的问题正是与这些蒲公英有关。 
为了简化起见,我们把所有的蒲公英看成一个长度为 n 的序列 (a1,a2,a3,...,an),其中ai为一个正整数,表示第i棵蒲公英的种类编号。 
而每次询问一个区间[l,r],你需要回答区间里出现次数最多的是哪种蒲公英,如果有若干种蒲公英出现次数相同,则输出种类编号最小的那个。 
注意,你的算法必须是在线的。 

输入

第一行两个整数n,m,表示有 n 株蒲公英, m 次询问。 
接下来一行 n 个空格分隔的整数ai,表示蒲公英的种类 
再接下来 m 行每行两个整数l0,r0.我们令上次询问的结果为 x(如果这是第一次询问,则x=0)。 
令l=(l0+x-1) mod n+1,r=(r0+x-1) mod n+1,如果l>r,则交换l,r。 
最终的询问区间为[l,r]。 

输出

输出m行。每行一个整数,表示每次询问的结果。

样例输入 Copy
6 3
1 2 3 2 1 2
1 5
3 6
1 5
样例输出 Copy
1
2
1
提示

对于20%的数据,保证1≤n,m≤3000。
对于100%的数据,保证1≤n≤40000,1≤m≤50000,1≤ai≤109


模板:我写了注释哦

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=200;//最多200块
typedef pair<int,int> PII;
int lenth,num;//分块长度和个数
int a[40010]; 
//b数组表示每个区间的左端点和右端点 
PII b[210];
//s[i][j]表示从1~i的块里数j出现的次数;
//f[i][j]表示第i块到第j块的众数;
//blk[i]表示下表i在第几块; 
int s[210][40010],f[210][210],blk[40010],t[40010];
int len,lsh[40010]; 
int n,m;
//离散化:将a里的每个数变成unique后数组的下表,既可以缩小数组大小,也可以方便根据下标
//        比较两个数的大小  
void lisanhua(){
	sort(lsh+1,lsh+n+1);
	len=unique(lsh+1,lsh+n+1)-(lsh+1);
	for(int i=1;i<=n;i++){
		int num=lower_bound(lsh+1,lsh+len+1,a[i])-(lsh+1);//注意这里是lsh+len+1而不是lsh+n+1 !!! 
		a[i]=num;
	}
}
//初始化 
void init(){
	num=n/lenth;
	if(n%lenth){
		num++;
	}
	for(int i=1;i<=num;i++){
		b[i].first=(i-1)*lenth+1;
		b[i].second=i*lenth;
	}
	b[num].second=n;
	for(int i=1;i<=num;i++){
		for(int j=b[i].first;j<=b[i].second;j++){
			s[i][a[j]]++;
			blk[j]=i;
		}
		//!!!!!!!!这里j的范围是0~len,而不是1~n; 
		for(int j=0;j<len;j++){
			s[i][j]+=s[i-1][j];
		}
	}
	int maxsum=0,maxpos=0,pos=0;
	for(int i=1;i<=num;i++){
		maxsum=0,maxpos=b[i].first,pos=b[i].first;
		for(int j=i;j<=num;j++){
			int mode = f[i][j - 1];
            for (int k = b[j].first; k <= b[j].second; k++) {
                int v1 = s[j][a[k]] - s[i - 1][a[k]], v2 = s[j][mode] - s[i - 1][mode];
                if (v1 > v2) mode = a[k];
                else if (v1 == v2) mode = min(mode, a[k]);
            }

            f[i][j] = mode;
		}
	}

}
int double_solve(int l,int r,int L,int R,int mod){
	for(int i=l;i<=r;i++){
		int v1=t[a[i]]+s[blk[R]-1][a[i]]-s[blk[L]][a[i]];
		int v2=t[mod]+s[blk[R]-1][mod]-s[blk[L]][mod];
//		cout<<a[i]<<" "<<mod<<" "<<v1<<" "<<s[blk[R]-1][mod]<<endl;
		if(v1>v2){
			mod=a[i];
		}else if(v1==v2){
			mod=min(mod,a[i]);
		}
	} 
	return mod;
}

//如果要求的左右断电距离小于lenth 
int one_solve(int l,int r){
	int maxsum=0,maxpos=a[l];
	for(int i=l;i<=r;i++){
		t[a[i]]++;
		if(t[a[i]]>maxsum){
			maxsum=t[a[i]];
			maxpos=a[i];
		}else if(t[a[i]]==maxsum){
			if(a[i]<maxpos){
				maxpos=a[i];
			}
		}
	}
	return lsh[maxpos+1];
}
 
int solve(int l,int r){
	memset(t,0,sizeof t);
	if(blk[r]-blk[l]<=1){
		return one_solve(l,r);//!
	}
	int L=b[blk[l]].second+1,R=b[blk[r]].first-1;
	for(int i=l;i<L;i++){
		t[a[i]]++;
	}
	for(int i=R+1;i<=r;i++){
		t[a[i]]++;
	}
	int mod=f[blk[l]+1][blk[r]-1];
	mod=double_solve(l,L-1,l,r,mod);
	mod=double_solve(R+1,r,l,r,mod);
	return lsh[mod+1];
}
int main()
{
	cin>>n>>m;
	lenth=sqrt(n);
	for(int i=1;i<=n;i++){
		cin>>a[i];
		lsh[i]=a[i];
	}
	lisanhua();
	init();
	int res=0;
	while(m--){
		int ll, rr;
        scanf("%d%d", &ll, &rr);
        //这一题里l和r与上次的答案有关,这个具体题目具体定 
        int l = (ll + res - 1) % n + 1, r = (rr + res - 1) % n + 1;
        if (l > r) swap(l, r);
        printf("%d\n", res = solve(l, r));
	}
	return 0;
}


(之后还写了一道求数的出现次数的题目,用这个模板就运行错误,awwww我再把它去写好!)

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值