歴史の研究(回滚)
歴史の研究
题面翻译
题目描述
IOI 国历史研究的第一人——JOI 教授,最近获得了一份被认为是古代 IOI 国的住民写下的日记。JOI 教授为了通过这份日记来研究古代 IOI 国的生活,开始着手调查日记中记载的事件。
日记中记录了连续 \(N\) 天发生的事件,大约每天发生一件。
事件有种类之分。第 \(i\) 天发生的事件的种类用一个整数 \(X_i\)
表示,\(X_i\) 越大,事件的规模就越大。
JOI 教授决定用如下的方法分析这些日记:
-
选择日记中连续的几天 \([L,R]\) 作为分析的时间段;
-
定义事件 \(A\) 的重要度 \(W_A\) 为 \(A\times T_A\),其中 \(T_A\) 为该事件在区间 \([L,R]\) 中出现的次数。
现在,您需要帮助教授求出所有事件中重要度最大的事件是哪个,并输出其重要度。
注意:教授有多组询问。
输入格式
第一行两个空格分隔的整数 \(N\) 和 \(Q\),表示日记一共记录了 \(N\) 天,询问有 \(Q\) 次。
接下来一行 \(N\) 个空格分隔的整数表示每天的事件种类。
接下来 \(Q\) 行,每行给出 \(L,R\) 表示一组询问。
输出格式
输出共有 \(Q\) 行,每行一个整数,表示对应的询问的答案。
数据范围
对于 \(100\%\) 的数据,\(1\le Q,N\le 10^5\),\(1\le X\le 10^9\),\(1\le L\le R\le 10^5\)。
样例输入 #1
5 5
9 8 7 8 9
1 2
3 4
4 4
1 4
2 4
样例输出 #1
9
8
8
16
16
样例 #2
样例输入 #2
8 4
9 9 19 9 9 15 9 19
1 4
4 6
3 5
5 8
样例输出 #2
27
18
19
19
样例 #3
样例输入 #3
12 15
15 9 3 15 9 3 3 8 16 9 3 17
2 7
2 5
2 2
1 12
4 12
3 6
11 12
1 7
2 6
3 5
3 10
7 10
1 4
4 8
4 8
样例输出 #3
18
18
9
30
18
15
17
30
18
15
18
16
30
15
15
解析
复习回滚莫队板子,
正常莫队有两种操作, \(add\) 和 \(del\) ,而回滚莫队用于处理一些只能进行其中一种操作的。
以本题为例,题意为求一个区间中一个数乘以它的出现次数的最大值。
也就是求 \(x \times cnt[x]\) 的最大值。
这时 \(add\) 操作很好实现,对于新加入的数更新 \(cnt\) 并求值,取最大值。
但是 \(del\) 操作很不好实现,因为删除一个数以后最大值可能不变,也可能变为次大值。
我们需要记录很多很多组。
因此为了避免 \(del\) 操作,我们选用回滚莫队。
进行分块后我们按照左端点整块排序,同一块内的按右端点排序。
对同一块进行讨论:
-
\(l\) 和 \(r\) 在同一块内,暴力求解。
-
\(l\) 和 \(r\) 不在同一块内,因为排过序,所以 \(r\) 一定是单调递增的。
但是 \(l\) 的顺序不确定,我们选取块右端点作为 基点 ,每次先动\(r\) ,然后记录 基点 到当前 \(r\) 的 \(cnt\).
动完 \(l\) 在把刚才记录的 \(cnt\) 腾过来。
code
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5+5;
int n,m;
int a[N],cnt[N],cntb[N],f[N],tot;
LL tmp,ans[N];
int l,r;
map<int,int> mp;
struct Q
{
int l,r,id;
} q[N];
int L[4000],R[4000],bl[N];
void bui()
{
int cnt = sqrt(n);
for(int i=1;i<=cnt;i++)
{
L[i]=n/cnt*(i-1)+1;
R[i]=n/cnt*i;
}
R[cnt]=n;
for(int i=1;i<=cnt;i++)
{
for(int j=L[i];j<=R[i];j++)
{
bl[j]=i;
}
}
}
void add(int x)
{
cnt[x]++;
tmp=max((LL)cnt[x]*f[x],tmp);
}
bool operator < (Q &x,Q &y)
{
if(bl[x.l]!=bl[y.l]) return bl[x.l]<bl[y.l];
else return x.r<y.r;
}
void mo_q()
{
for(int i=1;i<=m;)
{
memset(cnt,0,sizeof(cnt)); tmp=0;
int j=i; int right=bl[q[i].l];
while(right==bl[q[j].l]&&j<=m) j++;//找到左端点在同一块内的
r=R[right]; l=r+1;
while(i<j)
{
if(q[i].r<=R[right])//同一块内
{
for(int k=q[i].l;k<=q[i].r;k++) add(a[k]);
ans[q[i].id]=tmp;
tmp=0; memset(cnt,0,sizeof(cnt));
i++;
}
else//不同块
{
while(r<q[i].r) add(a[++r]);
memcpy(cntb,cnt,sizeof(cnt));
LL bbb=tmp;
while(l>q[i].l) add(a[--l]);
ans[q[i].id]=tmp; i++;
tmp=bbb;
l=R[right]+1;
memcpy(cnt,cntb,sizeof(cntb));
}
}
}
}
int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if(mp[a[i]]==0)
{
mp[a[i]]=++tot;
f[tot]=a[i];
a[i]=tot;
}
else a[i]=mp[a[i]];
}
bui();//注意调用
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
q[i]=Q{x,y,i};
}
sort(q+1,q+1+m);
mo_q();//注意调用
for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
return 0;
}
例题
思路类似
code
#include<bits/stdc++.h>
using namespace std;
const int N = 50005;
int n,m;
int a[N];
int L[300],R[300],bl[N];
int up[N],down[N],bdown[N],bup[N],ans[N];
int num,b;
struct Q
{
int l,r,id;
} q[N];
void bui()
{
int cnt=sqrt(n);
for(int i=1;i<=cnt;i++)
{
L[i]=n/cnt*(i-1)+1;
R[i]=n/cnt*i;
}
R[cnt]=n;
for(int i=1;i<=cnt;i++)
{
for(int j=L[i];j<=R[i];j++)
{
bl[j]=i;
}
}
}
void add(int x)
{
up[x]=up[x-1]+1;
down[x]=down[x+1]+1;
int t=up[x]+down[x]-1;
down[x-up[x]+1]=t;
up[x+down[x]-1]=t;
num=max(t,num);
}
bool cmp(Q &x,Q &y)
{
if(bl[x.l]==bl[y.l]) return x.r<y.r;
return bl[x.l]<bl[y.l];
}
void mo_q()
{
for(int i=1;i<=m;)
{
int j=i;
while(j<=m&&bl[q[i].l]==bl[q[j].l]) j++;
int now=R[bl[q[i].l]];
while(i<j&&q[i].r<=now)
{
for(int k=q[i].l;k<=q[i].r;k++) add(a[k]);
ans[q[i].id]=num;
num=0;
memset(up,0,sizeof(up));
memset(down,0,sizeof(down));
i++;
}
int r=now,l=now+1;
while(i<j)
{
while(r<q[i].r) add(a[++r]);
memcpy(bup,up,sizeof(up));
memcpy(bdown,down,sizeof(down));
b=num;
while(l>q[i].l) add(a[--l]);
ans[q[i].id]=num;
memcpy(up,bup,sizeof(up));
memcpy(down,bdown,sizeof(down));
num=b; l=now+1;
i++;
}
memset(up,0,sizeof(up));
memset(down,0,sizeof(down));
num=0;
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
bui();
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
q[i]={x,y,i};
}
sort(q+1,q+1+m,cmp);
mo_q();
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}
注意
莫队思路好想,方法暴力,熟悉暴力的打法吧。
\(\mathbb{mo \_ q}\)