看到这题就想到区间众数,然后又因为在线,就想到了分块的做法,再看一看数据范围1e5,显然可做,然后就找了找区间众数的代码,改了点细节,说几个修改的重点,首先因为数的范围最大是1e9,所以肯定需要离散化,其次块中记录的最大值应改为如题目所述的那样,为每个数乘以数出现的次数的最大值,最后且最关键的地方就是对边角的处理,在区间众数中,遍历左边角的时候,是将当前数的下标往右从ans起开始拓展,而在这题中,应该从当前数的下标往右从ans/a[i]个开始拓展(ans为中间块中的最大值,会根据边角的遍历而不断更新),右边角同理。(如若以上前置知识还未了解,移步https://www.luogu.com.cn/problem/P5048 ,先做做区间众数,学习一下分块,这题就迎刃而解啦!)
分享一下代码(代码中有详细注释)
#pragma GCC optimize(2) #include <bits/stdc++.h> #include <memory> using namespace std; #define sf(a) scanf("%d",&a) #define pf(a) printf("%d",a) #define ford(i,a,n) for(i=a;i<=n;i++) #define forb(i,n,a) for(i=n;i>=a;i--) #define mst(a,b) memset(a,b,sizeof(a)) typedef long long ll; typedef unsigned long long ull; typedef double db; #define pb push_back #define ddd cout<<"ddd"<<endl #define maxn 100010 #define inf 0x7fffffff //以上宏定义为个人码风习惯,如若影响阅读,十分抱歉 #define siz 317 //每个块的大小 const ll mod=1e9+7; ll mx[410][410];//mx[i][j]就是第i个块到第j个块的最大值 ll a[maxn];//离散后的数组 int n,m,blocks;//数组大小,询问次数,块大小 ll b[maxn];//离散前的数组 ll lef[410],righ[410];//每个块的左边界和右边界 ll book[maxn];//记录某个数的出现次数 int num[maxn];//标记当前下标属于哪个块 void init() { blocks=(n-1)/siz+1;// 计算总共有多少块 int i; ford(i,1,blocks) { lef[i]=righ[i-1]+1; righ[i]=i*siz; }//计算每个块的左边界和右边界 righ[blocks]=n; ford(i,1,blocks) { mst(book,0);//把标记数组初始化 int j; ford(j,lef[i],righ[i]) num[j]=i;//记录当前下标属于哪个块 ford(j,i,blocks)//遍历i到n,计算mx[i][j],j从i到n,每次最多遍历n个,最多遍历根号n次,所以此时复杂度为nsqrt(n) { int k; mx[i][j]=mx[i][j-1]; ford(k,lef[j],righ[j])//遍历 mx[i][j]=max(mx[i][j],b[a[k]]*(++book[a[k]])); } } } ll c[maxn]; vector<int>pos[maxn];//分块思想比较关键的地方来了,记录某个数出现的所有下标(按递增顺序记录) int rpos[maxn];//记录当前下标在 *当前下标的数* 的vector中的下标 (可能有点拗口,比如3这个数在下标为3,6,9这三个位置出现过,那么rpos3=0,rpos6=1,rpos9=2) int main() { ios::sync_with_stdio(false); cin.tie(0); cin>>n>>m; int i; map<ll,int>mp;//离散化用的,懒狗专用STL ford(i,1,n) { cin>>a[i]; c[i]=a[i];//离散化用的 } sort(c+1,c+n+1);//离散化 (可用unique替代,个人习惯) c[0]=-1; int now=0; ford(i,1,n) { if(c[i]!=c[i-1]) { b[++now]=c[i];//从离散化的数回到原数就是通过b数组 mp[c[i]]=now;//离散化 } } ford(i,1,n) { a[i]=mp[a[i]];//把原数离散化 pos[a[i]].pb(i);//把下标记录到vector中 rpos[i]=pos[a[i]].size()-1;//意义如上 } init(); ll ans=0; mst(book,0);//分块的时候有用到book数组,所以此时重新初始化 while(m--) { ll l,r; cin>>l>>r; l^=ans;//如题 r^=ans; l=l%n+1; r=r%n+1; if(l>r)//如题 swap(l,r); ans=0;//重置最大值 if(num[l]==num[r])//如果左右边界在同一个块中,直接暴力,块大小为sqrt(n),所以复杂度可以接受 { ford(i,l,r) ans=max(ans,b[a[i]]*(++book[a[i]])); ford(i,l,r) book[a[i]]=0;//初始化,以免影响之后操作 } else { ans=mx[num[l]+1][num[r]-1];//把除了边角的部分先记录下来 ford(i,l,righ[num[l]]) { int now=rpos[i];//求出当前下标在vector中的下标 if(b[a[i]]==0)//避免除0错误 continue; ll temp=ans/b[a[i]];//求出如果要比当前最大值大的所需最小数量 while(now+temp<pos[a[i]].size()&&pos[a[i]][now+temp]<=r)//分块思想最重要的地方,查询当前数之后的第temp个数的下标是否在r范围内,如果是的话继续增加temp,看看最多能增加多少 { temp++; ans=max(ans,b[a[i]]*temp);//求出最大值 } } ford(i,lef[num[r]],r)//意义同左边角,同理可得 { int now=rpos[i]; if(b[a[i]]==0) continue; ll temp=ans/b[a[i]]; while(now-temp>=0&&pos[a[i]][now-temp]>=l) { temp++; ans=max(ans,b[a[i]]*temp); } } //关于temp的暴力为什么会在sqrt(n)内,因为每次枚举完temp之后,答案是一直在增加的,那么下一次的起点就更加靠后,需要遍历的次数也变小了 } cout<<ans<<endl; } }