我一开始是直接把E的代码改改就交的一发过,在线分块做法,286ms的速度很优秀,后来讲题时遇到新的算法——莫队,这题本来是专门为离线做法准备的,虽然看起来并没有卡在线做法,但还是得学一下。
先谈谈我对莫对算法的理解吧,都说莫队是一种优雅的暴力,用于求解区间内问题,因为区间在不停变换,我们如果用两个指针维护我们需要查询的区间(l 和 r),有以下三种情况。
- 无序,让l和r乱跳,时间O(n^2q)爆TLE!
- 排序后莫队,让l有序,r乱跳,时间O(NQ*LG(N)) 还是TLE!
- 考虑分块优化,如果快排时的左端点l和右端点r在同一区间内,即l div sqrt(n)=r div sqrt(n)。
那么l,r在同一区间,直接以 query[ ].r 数组排序为第一关键字排序即可。
否则以 query[ ].l 数组为第一关键字排序。
分块后让两个指针在区间板块内左右横跳,这就是莫队的想法。
因为要排序,提前需要准备的是写一个排序规则的函数,规则说过了,代码如下
struct node{ //在构造节点时重载一下node类型的排序规则就行了
int l, r, id;
bool operator < (const node &x)
{return l/divlen == x.l/divlen ? r < x.r : l < x.l;}
}query[maxn];
其中id是代表当前询问是原询问序列第几位,因为离线排过序,但是输出还是要遵循原序列顺序,所以需要用id来打标记,在建立一个辅助数组ans[ ]来依次存入ans[query[ ].id]的答案。
前置准备到此为止,现在上莫队核心代码:
FOR(i, 1, m) //来自莫队的左右横跳
{
while(r < query[i].r) Add(++r);
while(l > query[i].l) Add(--l);
while(r > query[i].r) Erase(r--);
while(l < query[i].l) Erase(l++);
ans[query[i].id] = ret;
}
排过序后每次移动到对应的左右端点,这个过程中答案的修改用ret存,最后放在ans[ ]里,修改规则用Add()函数和Erase()函数存,比如右指针右移至右端点,这个过程中存在判断添加进区间的新点的过程,称为Add,其他情况同理思考。
我的Add()和Erase()函数写法如下:
//cnt[i]表示i这个数在[l,r]区间中出现的次数,sum[i]表示 区间中cnt[x]=i的数的个数
void Add(int pos){
int x = a[pos];
sum[cnt[x]]--;
sum[++cnt[x]]++;
ret = max(ret, cnt[x]);
return;
}
void Erase(int pos){
int x = a[pos];
sum[cnt[x]]--;
if(ret == cnt[x]) //如果当前的数就是最大数ret,那么就有对应的删减判断措施
if(sum[cnt[x]]==0) //如果最大数的副本数量已经减为零
ret--;
sum[--cnt[x]]++;
return;
}
AC代码如下:
#include<bits/stdc++.h>
#define maxn 200005
#define maxm 200005
#define FOR(a, b, c) for(int a=b; a<=c; a++)
#define hrdg 1000000007
#define inf 2147483647
#define llinf 9223372036854775807
#define ll long long
#define pi acos(-1.0)
#define ls p<<1
#define rs p<<1|1
#define max(a, b) a>b?a:b
using namespace std;
const int divlen = 405;
const int mdiv = maxn/divlen + 5;
int n, m, k, l=1, r;
ll ret, ans[maxn];
int pre_lsh[maxn], a[maxn], a1[maxn];
int tot;
int cnt[maxn], sum[maxn];
inline int read(){
char c=getchar();long long x=0,f=1;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
struct node{
int l, r, id;
bool operator < (const node &x)
{
return l/divlen == x.l/divlen ? r < x.r : l < x.l;
}
}query[maxn];
void discret(){ //离散化
sort(pre_lsh+1, pre_lsh+1+n);
tot = unique(pre_lsh+1, pre_lsh+1+n) - (pre_lsh+1);
FOR(i, 1, n)
a[i] = lower_bound(pre_lsh+1, pre_lsh+1+tot, a1[i]) - pre_lsh;
}
void Add(int pos){
int x = a[pos];
sum[cnt[x]]--;
sum[++cnt[x]]++;
ret = max(ret, cnt[x]);
return;
}
void Erase(int pos){
int x = a[pos];
sum[cnt[x]]--;
if(ret == cnt[x])
if(sum[cnt[x]]==0)
ret--;
sum[--cnt[x]]++;
return;
}
int main()
{
n = read();
m = read();
FOR(i, 1, n)
{
a1[i] = read();
pre_lsh[i] = a1[i];
}
discret();
FOR(i, 1, m)
{
query[i].l = read();
query[i].r = read();
query[i].id = i;
}
sort(query+1, query+1+m);
FOR(i, 1, m) //来自莫队的左右横跳
{
while(r < query[i].r) Add(++r);
while(l > query[i].l) Add(--l);
while(r > query[i].r) Erase(r--);
while(l < query[i].l) Erase(l++);
ans[query[i].id] = ret;
}
FOR(i, 1, m)
printf("%lld\n", ans[i]);
return 0;
}
不愧是离线算法,我来康康跑了多久啊,1084ms……
好吧我的莫队写的太垃圾了,离线处理连在线都跑不过,wsl
这里贴一发在线做法(同E题):
#include<bits/stdc++.h>
#define maxn 200005
#define maxm 200005
#define FOR(a, b, c) for(int a=b; a<=c; a++)
#define hrdg 1000000007
#define inf 2147483647
#define llinf 9223372036854775807
#define ll long long
#define pi acos(-1.0)
#define ls p<<1
#define rs p<<1|1
#define max(a, b) a>b?a:b
using namespace std;
const int divlen = 405; //分块长度略小于sqrt(maxn)
const int mdiv = maxn/divlen + 5;
int pre_lsh[maxn], a[maxn], a1[maxn], temp, ans;
int n, m, ql, qr, lb, rb, l[maxn], r[maxn], bel[maxn], tot, divnum;
int id[maxn];
vector<int> v[maxn];
int cnt[maxn], MAX[mdiv][mdiv];
inline int read(){
char c=getchar();long long x=0,f=1;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
void discret(){ //离散化
sort(pre_lsh+1, pre_lsh+1+n);
tot = unique(pre_lsh+1, pre_lsh+1+n) - (pre_lsh+1);
FOR(i, 1, n)
{
a[i] = lower_bound(pre_lsh+1, pre_lsh+1+tot, a1[i]) - pre_lsh;
id[i] = v[a[i]].size(); //id[i]该数在vector中的位置
v[a[i]].push_back(i); //vector存储每种数出现位置
}
}
int main()
{
n = read();
m = read();
FOR(i, 1, n)
{
a1[i] = read();
pre_lsh[i] = a1[i];
bel[i] = (i-1) / divlen + 1;
}
divnum = bel[n]; //分块数量
discret();
FOR(i, 1, divnum)
{
l[i] = (i-1) * divlen; //每个板块起始位置
r[i] = l[i] + divlen - 1; //结束位置
}
l[1] = 1; r[divnum] = n;
FOR(i, 1, divnum)
{
memset(cnt, 0, sizeof(cnt));
int mama = 0, now = i;
FOR(j, l[i], n)
{
cnt[a[j]]++; //线性数过去
mama = max(mama, cnt[a[j]]);
if(j == r[now])
{
MAX[i][now] = mama;
now++;
}
}
}
while(m--)
{
ql = read(); qr = read();
lb = bel[ql], rb = bel[qr];
ans = 0;
if(lb == rb) //同一分块内
{
FOR(i, ql, qr)
{
int num = a[i], siz = v[num].size(), pos = id[i];
while(pos + ans < siz && v[num][pos+ans] <= qr)
ans++;
}
}
else
{
if(lb + 1 < rb) //先统计中间板块中预处理过的众数大小
ans = MAX[lb+1][rb-1];
FOR(i, ql, r[lb]) //边界板块
{
int num = a[i], siz = v[num].size(), pos = id[i];
while((pos+ans) < siz && v[num][pos+ans] <= qr)
ans++;
}
FOR(i, l[rb], qr)
{
int num = a[i], pos = id[i];
while(pos >= ans && v[num][pos-ans] >= ql)
ans++;
}
}
printf("%d\n", ans);
}
return 0;
}