题解
非常有幸在某谷上成为了跑得最快的一个至少在笔者写题解时是如此的,所以来水一篇题解。 顺便批判一下某个为了让题目价值最大化的人,居然把公开了的题隐藏了又拿出来卖(详见某谷评论区),笔者不幸成为了受害者。(;′д`)ゞ
好的,那让我们开始讲讲这道题吧。 首先让我们想想颜色数量比较少,譬如颜色数为
2
2
2 的情况,有什么比较好的解决方法。 由于它要求的是区间段内一种颜色的出现次数严格大于其它颜色的数量,我们不妨将该种颜色的值看作
1
1
1,其它颜色的值看作
?
1
-1
?1,那么我们的要求就转化为了区间之和要大于
0
0
0。我们可以考虑用哪个前缀和的差分来维护区间,这样我们就相当于要对每个前缀和统计它前面有多少个比它小,经典的二维偏序问题。
那许多颜色时,如果我们再一个颜色一个颜色的这么做不就
O
(
n
2
)
O\left(n^2\right)
O(n2) 了吗?这不__T__?飞。 没事,我们观察到对于每个颜色真正有用的区间是非常少的,经常会出现很长一段区间一个该颜色的点都没有的情况。 我们可以对于该颜色可能成为绝对众数(也就是它的),找到它的可能区间,只考虑这个区间内众数的计算。对于这个可能的区间,我们可以去找它的最左左端点与最右右端点,把它们之间看作这种颜色的一个可能区间。 下面给出一种比较简单的找法,从左到右枚举每个该颜色的位置,选中它左边第一个尚未被选择到的不是该颜色的位置。再从右到左枚举每个该颜色的位置,选择它右边第一个尚未被选择的不是该颜色的位置。 显然,所有选择位置与该颜色的位置构成的连通块就是一个可能的区间。你再选连通块外的任意一个点,必然不可能构成包含该连通块中点的总和为正的区间,也就不会对答案产生任何贡献。
好的,这样我们就能非常简单地求出我们的可能区间了。可以发现,这个可能区间有一些非常好的性质:
- 总长度是
O
(
n
)
O\left(n\right)
O(n) 级别的。显然,每个位置最多只会让两个与其同色的点被选择,总长度不会超过
3
n
3n
3n。
- 每一个位置最多被
O
(
log
?
n
)
O\left(\log n\right)
O(logn) 个不同色的可能区间覆盖。由于我们是每种颜色计算可能区间时是计算的一个连通块,所以每种颜色的可能区间时不想交的,覆盖每个位置的可能区间最多只有一个。
先假设该位置时可能区间的某个端点,将所有可能区间按长度排序,下一个区间的颜色要成为绝对众数,它包含的该值颜色必然会超过其它颜色,可以发现,这样它是在不断翻倍的,是
O
(
log
?
n
)
O\left( \log n\right)
O(logn) 级别。即使该位置不是可能区间的端点,也最多会扩大常数倍,该结论仍成立。
首先,只有当询问区间与可能区间相交时,可能区间才会对答案产生贡献。考虑询问区间与可能区间相交的三种情况。
- 我们的询问区间包含了我们的可能区间。
- 我们的询问区间被我们的可能区间包含。
- 两者不存在包含关系。
其中,对于第一种情况,显然就是个二维数点问题。我们可以先将每个可能区间的总贡献值算出来,然后加在点上。排序后用一个树状数组就可以
O
(
n
log
?
n
)
O\left(n\log n\right)
O(nlogn) 地维护。 对于第二种情况,我们可以询问区间肯定包含可能区间的某个前后缀,我们不妨考虑把这个前后缀的答案预处理出来,再在询问区间的左右端点上,枚举处于这种情况的可能区间有哪些,将前后缀的贡献加入答案,显然,这部分时间复杂度是
O
(
n
log
?
n
)
O\left(n\log n\right)
O(nlogn)。
诶,单纯预处理一个全局或者前后缀的逆序对数量是
O
(
n
log
?
n
)
O\left(n\log n\right)
O(nlogn) 的二维数点吗?显是可以优化的,毕竟我们这里的值的变化时连续的,也感谢
C
i
r
n
o
_
9
\rm Cirno\_9
Cirno_9 为我点明了这个方法。 可以发现,我们值的变化都是形如
k
=
1
/
?
1
k=1/-1
k=1/?1 的直线。大概是这个样子
我们可以考虑记录
c
n
t
i
cnt_i
cnti? 表示前缀值和为
i
i
i 的前缀数量,再记录一个
n
o
w
now
now 表示比右端点小的前缀的数量。 假设我们现在右端点处值为
x
x
x,如果它右移一步,增大
1
1
1,也就是说现在比它小的点前缀数量增加了
c
n
t
x
cnt_x
cntx?,我们就将
n
o
w
now
now 加上
c
n
t
x
cnt_x
cntx?。再将现在的
n
o
w
now
now 计入总的答案。 这样,我们就可以做到
O
(
n
)
O\left(n\right)
O(n) 地维护全局及前后缀的答案了。
我们现在再来考虑我们的第三种情况,显然,这就相当于查询我们的询问区间在全局区间中对应的区间逆序对数量,这不是可以考虑莫队吗? 我们可以将所有的这样的询问离线下来,对于每个可能区间单独做莫队,依旧用我们上面的方法实时维护答案。 每个长度为
n
′
n'
n′ 的,询问数为
m
′
m'
m′ 的区间的时间复杂度为
O
(
n
′
m
′
)
O\left(n'\sqrt{m'}\right)
O(n′m′
?),第三类的总时间复杂度为
O
(
n
m
)
O\left(n\sqrt{m}\right)
O(nm
?)。
最后只需要把每一类对该询问区间的贡献加在一起,就可以得到该询问区间的总答案了。 总时间复杂度为
O
(
n
m
+
(
m
+
n
)
log
?
n
)
O\left(n\sqrt{m}+(m+n)\log n\right)
O(nm
?+(m+n)logn)。
源码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
typedef pair<LL,LL> pii;
#define MAXN 1000005
#define MAXM 3000005
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
#define lson (rt<<1)
#define rson (rt<<1|1)
#define lowbit(x) (x&-x)
const int mo=998244353;
const int inv2=5e8+4;
const int jzm=2333;
const int zero=15;
const int INF=0x3f3f3f3f;
const double Pi=acos(-1.0);
const double eps=1e-9;
const int orG=3,ivG=332748118;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
_T f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
x*=f;
}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1)t=1ll*a*t%p;a=1ll*a*a%p;s>>=1;}return t;}
int n,m,a[MAXN],stak,stb[MAXM],stbk,tot,cnt[MAXM],bg[MAXM],ed[MAXM],totd,ord[MAXN],val[MAXN],n1;
LL pre[MAXM],suf[MAXM],sum[MAXN],ans[MAXN],tr[MAXN];pii sta[MAXN];
vector<int>vec[MAXN],vp[MAXN];
struct ming{int l,r,d;}s[MAXM],p[MAXN],d[MAXN];
bool cmp1(ming x,ming y){return x.r<y.r;}
bool cmp2(ming x,ming y){
if(x.l/n1==y.l/n1)return (x.l/n1&1)?x.r>y.r:x.r<y.r;
return x.l<y.l;
}
void insert(int pos,LL aw){while(pos)tr[pos]+=aw,pos-=lowbit(pos);}
LL query(int pos){LL res=0;while(pos<=n)res+=tr[pos],pos+=lowbit(pos);return res;}
int main(){
read(n);read(m);
for(int i=1;i<=n;i++)read(a[i]),vec[a[i]].pb(i);
for(int i=1;i<=n;i++){
int siz=vec[i].size();if(!siz)continue;int las=stak=stbk=0;
for(int j=0;j<siz;j++){
int x=vec[i][j];if(x>las+1)sta[++stak]=mkpr(las+1,x-1);
las=x;if(stak)stb[++stbk]=sta[stak].sec,sta[stak].sec--;
if(stak&&sta[stak].sec<sta[stak].fir)stak--;stb[++stbk]=x;
}
las=n+1;stak=0;
for(int j=siz-1;j>=0;j--){
int x=vec[i][j];if(x<las-1)sta[++stak]=mkpr(x+1,las-1);
las=x;if(stak)stb[++stbk]=sta[stak].fir,sta[stak].fir++;
if(stak&&sta[stak].fir>sta[stak].sec)stak--;
}
sort(stb+1,stb+stbk+1);stbk=unique(stb+1,stb+stbk+1)-stb-1;vec[i].clear();
for(int l=1,r;l<=stbk;l=r+1){
r=l;while(r<stbk&&stb[r+1]-stb[l]==r-l+1)r++;
s[++tot]=(ming){stb[l],stb[r],i};
}
}
sort(s+1,s+tot+1,cmp1);
for(int i=1;i<=tot;i++){
bg[i]=ed[i-1]+1;ed[i]=bg[i]+s[i].r-s[i].l;
int nw=n,dn=n,up=n,now=0,dt=s[i].d;cnt[n]++;
for(int j=bg[i],k=s[i].l;j<=ed[i];j++,k++){
if(a[k]==dt)now+=cnt[nw];else now-=cnt[nw-1];
nw=nw+(a[k]==dt?1:-1);cnt[nw]++;
if(j==bg[i])pre[j]=1ll*dt*now;else pre[j]=pre[j-1]+1ll*dt*now;
dn=min(dn,nw);up=max(up,nw);
}
for(int j=dn;j<=up;j++)cnt[j]=0;
sum[i]=pre[ed[i]];now=0;dn=up=nw=n;cnt[n]++;
for(int j=ed[i],k=s[i].r;j>=bg[i];j--,k--){
if(a[k]==dt)now+=cnt[nw];else now-=cnt[nw-1];
nw=nw+(a[k]==dt?1:-1);cnt[nw]++;
if(j==ed[i])suf[j]=1ll*dt*now;else suf[j]=suf[j+1]+1ll*dt*now;
dn=min(dn,nw);up=max(up,nw);
}
for(int j=dn;j<=up;j++)cnt[j]=0;
for(int j=s[i].l;j<=s[i].r;j++)vp[j].pb(i);
}
for(int i=1;i<=m;i++){
read(p[i].l),read(p[i].r);p[i].d=i;
int l=p[i].l,r=p[i].r,sizl=vp[l].size(),sizr=vp[r].size();
for(int j=0;j<sizl;j++){int x=vp[l][j];if(s[x].r<=r)ans[i]+=suf[bg[x]+l-s[x].l];else vec[x].pb(i);}
for(int j=0;j<sizr;j++){int x=vp[r][j];if(s[x].l>l)ans[i]+=pre[bg[x]+r-s[x].l];}
}
sort(p+1,p+m+1,cmp1);
for(int i=1;i<=m;i++)ord[p[i].d]=i;
for(int i=1,j=1,k=1;i<=n;i++){
while(k<=m&&p[k].r<=i)ans[p[k].d]+=query(p[k].l+1),k++;
while(j<=tot&&s[j].r<=i)insert(s[j].l,sum[j]),j++;
}
for(int i=1;i<=tot;i++){
int siz=vec[i].size(),up=n,dn=n;if(!siz)continue;
for(int j=0;j<siz;j++)d[j+1]=p[ord[vec[i][j]]];
n1=sqrt(s[i].r-s[i].l+1);sort(d+1,d+siz+1,cmp2);
int L=s[i].l,R=L-1,nowl=0,nowr=0;LL now=0;cnt[n]++;val[R]=n;
for(int j=s[i].l;j<=s[i].r;j++)
val[j]=val[j-1]+(a[j]==s[i].d?1:-1),
up=max(up,val[j]),dn=min(dn,val[j]);
for(int j=1;j<=siz;j++){
int l=d[j].l,r=d[j].r;
while(R<r){
if(val[R+1]>val[R])nowr+=cnt[val[R]];else nowr-=cnt[val[R+1]];
now+=nowr;R++;if(val[L-1]<val[R])nowl++;cnt[val[R]]++;
}
while(L>l){
if(val[L-1]>val[L-2])nowl+=cnt[val[L-1]];else nowl-=cnt[val[L-2]];
now+=nowl;L--;if(val[L-1]<val[R])nowr++;cnt[val[L-1]]++;
}
while(R>r){
now-=nowr;cnt[val[R]]--;
if(val[R-1]>val[R])nowr+=cnt[val[R]];else nowr-=cnt[val[R-1]];
if(val[L-1]<val[R])nowl--;R--;
}
while(L<l){
now-=nowl;cnt[val[L-1]]--;
if(val[L-1]>val[L])nowl+=cnt[val[L-1]];
else nowl-=cnt[val[L]];
if(val[L-1]<val[R])nowr--;L++;
}
ans[d[j].d]+=1ll*s[i].d*now;
}
for(int j=dn;j<=up;j++)cnt[j]=0;
for(int j=s[i].l-1;j<=s[i].r;j++)val[j]=0;
}
for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
return 0;
}
谢谢!!!
|