【AcWing 249. 蒲公英】
题意:
长度为n的序列,给定区间,求区间众数,如果出现次数相同,输出编号最小的
题解:
区间众数,不带修改,强制在线(否则可以莫队)
没有什么好办法那就只能暴力分块
分块,预处理任意两个大块之间的众数,相当于求出所有后缀的众数情况(枚举左块的端点,n*sqrt(n))
询问[l,r]中众数肯定是大块idl+1 ~ idr-1的众数,或者小块中的某个数
这一共是有sqrt(n)个候选答案,需要统计其在区间[l,r]内的出现次数,
第一个方法是用adj[x]记录x的所有出现次数,那么x在[l,r]内的出现次数就是:
upper_bound(adj[x].begin(), adj[x].end(), r) - lower_bound(adj[x].begin(), adj[x].end(), l)
这个做法的复杂度是O(nsqrt(n)logn)
第二个方法是预处理每个数在所有块中出现次数的前缀(n*sqrt(n)),这样直接就可以用差分来求次数,可以省掉第一个方法中的查找(去掉log),复杂度O(nsqrt(n))
代码:
代码有详细注释
貌似方法一会超时,只有方法二可以过
方法一:
#pragma optimize("Ofast")
#include<bits/stdc++.h>
#define MAXN 40005
#define MAXB 2005
using namespace std;
typedef long long ll;int N,M,B;
int a[MAXN], c[MAXN];
int d[MAXB][MAXB];vector<int> adj[MAXN];void init(){int cnt[MAXN], res;for(int l=0;l<=N;l+=B)//枚举左块 {if(l==0) continue;memset(cnt, 0, sizeof(cnt));res = 0;for(int r=l;r<=N;r++)//枚举右边界 {++cnt[a[r]];bool f1=cnt[a[r]] > cnt[res];//出现次数最多 bool f2=( cnt[a[r]] == cnt[res] && a[r] < res);//出现次数一样,编号更小 if(f1||f2) res = a[r];if((r+1)%B==0 || r==N) d[l/B][r/B] = res;//第l/B块到第r/B块的众数是res }}
}inline int num(const int& l, const int& r, const int& x){//在[l,r]范围内x出现几次 return upper_bound(adj[x].begin(), adj[x].end(), r) - lower_bound(adj[x].begin(), adj[x].end(), l);
}int query(int l, int r){int ans = 0;int idl = l/B, idr = r/B;if(idl-1 < idr) //如果idl在idr的左侧(或者一样) ans = d[idl+1][idr-1];int n = num(l,r,ans), n1;//n和n1表示出现数量//ans为出现次数最多的数的编号 //-----以下为分块的常规操作 if(idl==idr){for(int i=l;i<=r;i++){n1 = num(l,r,a[i]);//查看块内是否有数字大于n if(n < n1 || n == n1 && a[i] < ans) ans = a[i], n = n1;}}else{for(int i=l;i<(idl+1)*B;i++){n1 = num(l,r,a[i]);if(n < n1 || n == n1 && a[i] < ans) ans = a[i], n = n1;}for(int i=idr*B;i<=r;i++){n1 = num(l,r,a[i]);if(n < n1 || n == n1 && a[i] < ans) ans = a[i], n = n1;}}return c[ans];
}int main(){scanf("%d%d", &N, &M);B = sqrt(N); for(int i=1;i<=N;i++){scanf("%d", &a[i]);}memcpy(c,a,sizeof(a));sort(c+1, c+1+N);for(int i=1;i<=N;i++){a[i] = lower_bound(c+1, c+1+N, a[i]) - c;adj[a[i]].push_back(i);//a[i]在哪些位置出现过 }//init();int last = 0, l, r;while(M--){scanf("%d%d", &l, &r);l = (l+last-1)%N + 1;r = (r+last-1)%N + 1;if(l > r) swap(l, r);//cout<<"l r "<<l<<" "<<r<<endl;last = query(l,r);printf("%d\n", last);}return 0;
}
方法二:
#pragma optimize("Ofast")
#include<bits/stdc++.h>
#define MAXN 40005
#define MAXB 2005
using namespace std;
typedef long long ll;int N,M,B;
int a[MAXN], c[MAXN];
int s[MAXB][MAXN], d[MAXB][MAXB];inline int num(int idl, int idr, int x){//利用差分求出现次数 if(idl>idr) return 0;if(idl==0) return s[idr][x];else return s[idr][x] - s[idl-1][x];
}
int cnt[MAXN];
void init(){for(int i=0;i<N;i++){if(i>0 && i%B==0){//到了下一块 for(int j=0;j<N;j++){s[i/B][j] = s[i/B-1][j];//赋值前一块的情况 }}++s[i/B][a[i]];}// 上述内容为预处理每个数在所有块中出现次数的前缀 //下方内容为求出任意2个大块之间的众数int res;for(int l=0;l<N;l+=B){memset(cnt, 0, sizeof(cnt));res = 0;for(int r=l;r<N;r++){++cnt[a[r]];if(cnt[a[r]] > cnt[res] || cnt[a[r]] == cnt[res] && a[r] < res) res = a[r];if((r+1)%B==0 || (r+1)==N) d[l/B][r/B] = res;}}
}int query(int l, int r){int ans = 0;int idl = l/B + 1, idr = r/B - 1;if(idl <= idr) ans = d[idl][idr];int n = num(idl,idr,ans), n1;if(l/B == r/B){for(int i=l;i<=r;i++) ++cnt[a[i]];for(int i=l;i<=r;i++){if(cnt[a[i]]==0) continue;n1 = num(idl, idr, a[i]) + cnt[a[i]];if(n < n1 || n == n1 && a[i] < ans) ans = a[i], n = n1;cnt[a[i]] = 0;}}else{for(int i=l;i<idl*B;i++) ++cnt[a[i]];for(int i=(idr+1)*B;i<=r;i++) ++cnt[a[i]];for(int i=l;i<idl*B;i++){if(cnt[a[i]]==0) continue;n1 = num(idl, idr, a[i]) + cnt[a[i]];if(n < n1 || n == n1 && a[i] < ans) ans = a[i], n = n1;cnt[a[i]] = 0;}for(int i=(idr+1)*B;i<=r;i++){if(cnt[a[i]]==0) continue;n1 = num(idl, idr, a[i]) + cnt[a[i]];if(n < n1 || n == n1 && a[i] < ans) ans = a[i], n = n1;cnt[a[i]] = 0;}}return c[ans];
}int main(){//ios::sync_with_stdio(0);scanf("%d%d", &N, &M);B = sqrt(N); for(int i=0;i<N;i++){scanf("%d", &a[i]);}memcpy(c,a,sizeof(a));sort(c, c+N);for(int i=0;i<N;i++){a[i] = lower_bound(c, c+N, a[i]) - c;}//init();memset(cnt, 0, sizeof(cnt));int last = 0, l, r;while(M--){scanf("%d%d", &l, &r);l = (l+last-1)%N + 1;r = (r+last-1)%N + 1;if(l > r) swap(l, r);--l; --r;//cout<<"l r "<<l<<" "<<r<<endl;last = query(l,r);printf("%d\n", last);}return 0;
}