I_AM_CIMOTA
2025-01-07 20:29:06
本题大致可以分为两个部分。
由于所有元素互不相同,那么
于是枚举区间长度,每个长度的区间贡献都可以
根据数据范围猜做法。
注意到
怎样快速计算
我们在求
那么加上树状数组维护区间和,移动端点花费的时间为
假设现在
我们在回过头来观察原式:
对于某一个
现在,我们知道了每个
上式中
这个东西可以抽象为:一条斜率为
那么对于一个确定的
这个时间复杂度还是不够优秀,考虑优化。因为我们猜测的目标时间复杂度与
我们画一张图:
显然,如果
与前面结合起来,复杂度瓶颈在于移动断点的
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5,Mod=998244353;
int n,m,ans,tot,a[N],pos[N],las[N],pt[N];
bool vis[N];
namespace BIT{//树状数组
int t[N];
void upd(int x,int add){for(;x<=n;x+=x&(-x))t[x]+=add;}
int qry(int x){
int res=0;
for(;x;x-=x&(-x))res+=t[x];
return res;
}
int qry(int l,int r){return qry(r)-qry(l-1);}
}
using namespace BIT;
namespace CONV{//凸壳
int tp;
struct Comp{
int x,y;
}p[N],st[N];
struct Line{
Comp P,Q;
double k(){return 1.0*(Q.y-P.y)/(Q.x-P.x);}
};
void get_conv(){
tp=0;
st[++tp]=p[1];
if(tot==1)return;
st[++tp]=p[2];
for(int i=3;i<=tot;i++){
Line pre={st[tp-1],st[tp]},nw={st[tp],p[i]};
while(pre.k()<nw.k()){
tp--;
if(tp==1)break;
pre={st[tp-1],st[tp]},nw={st[tp],p[i]};
}
st[++tp]=p[i];
}
}
}
using namespace CONV;
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
if(!vis[a[i]])m++,vis[a[i]]=1;
las[i]=pos[a[i]];
pos[a[i]]=i;
}
if(m<=800){//subtask1,3,4,5
for(int i=1;i<=n;i++){
if(las[i])upd(las[i],-1);
upd(i,1);
for(int j=1;j<=m;j++)while(qry(pt[j]+1,i)==j)pt[j]++;//移动断点
tot=0;
for(int j=1;j<=m;j++)if(pt[j])p[++tot]={j,pt[j]*j+j};
get_conv();
int lasR=0;//为了不重复统计,直接每次把左界设为上一次的右界加1
for(int j=tp;j>=1;j--){
int L=lasR+1,R=i;
if(j>1){
Line A={st[j-1],st[j]};
R=min(R,(int)floor(A.k()));
}
if(j<tp){
Line A={st[j],st[j+1]};
L=max(L,(int)ceil(A.k()));
}
if(L>R)continue;
ans+=-((L+R)*(R-L+1)/2)*st[j].x+(R-L+1)*st[j].y;
ans=(ans%Mod+Mod)%Mod;
lasR=R;
}
}
printf("%lld\n",ans);
}
else{//subtask2
for(int i=1;i<=n;i++){
if(i&1)ans+=((i+1)/2)*((i+1)/2)%Mod*(n-i+1)%Mod;
else ans+=(i/2)*(i/2+1)%Mod*(n-i+1)%Mod;
ans%=Mod;
}
printf("%lld\n",ans);
}
return 0;
}