G-数据结构-G

 


T1 距离

image

首先这题部分分很多,直接 O(n2) 枚举点对,在树上差分即可获得 70 分。

那么正解几乎和部分分就没什么关系了。

首先看到

ansu=xsubtreeuysubtreeumin(|axay|,|bxby|)

中带了一个 min 的操作,这让我们很不好用数据结构来维护,所以这个时候就得转化题意。

a+b=min(a,b)+max(a,b)

这个式子是显而易见的,那么通过这个式子我们就可以将 min 操作转换为 max 操作和简单求和操作。

但是我们转了个dan啊,取 min 和 取 max 不是一样难以维护吗。

你说得对,但是,取 max 操作还可以再次进行转换。

在原本问题中,一个特征值为 ai,bi 的点,可以映射到平面直角坐标系上的一个点 (ai,bi) ,那么两个点 (ai,bi)(aj,bj) 之间的贡献就是 min(|aiaj|,|bibj|) ,也就是 |aiaj|+|bibj|max(|aiaj|,|bibj|)

那么 max(|aiaj|,|bibj|) 就是 切比雪夫距离

学习切比雪夫距离可看这篇博客

然后人类(反正不是我)发现 切比雪夫距离可以转化为曼哈顿距离 ,也就意味着可以将 取 max 操作转化为 绝对值求和操作,那么整道题就可以转化为 4个绝对值求和操作 。

怎么转呢?

我们通过图像来看,这是平面直角坐标系上所有与原点的切比雪夫距离为 1 的点:

image

一个边长为 2 的正方形的四条边。

然后我们在看看平面直角坐标系上所有与原点的曼哈顿距离为 1 的点:

image

一个边长为 2 的正方形的四条边。

既然都是正方形,那么我给他转一下,然后再将边长乘上 22 即可。

按理来说矩阵就是用来将一个坐标系旋转的,但是我根本不会好吧。

而且因为这是二维的,我们可以用虚数导一下。

x+yi 表示点 (x,y) ,一个虚数 乘上一个 虚数 的含义就是:两者的模长相乘,辐角主值相加。

所以将一个二维点进行旋转可以用虚数来导。那么将 (x,y) 逆时针旋转 45 °,再乘以 22 ,就相当于 乘上 12+12i 。点 (x,y) 也就变成了 (xy2,x+y2)

当然你旋转 45°、135°、-45°、-135° 都可以,一般会把 (x,y) 转化为 (x+y2,xy2)

接下来呢,设 a1i=ai+bi2,b1i=aibi2,整道题都转化为了一个式子:

ansu=xsubtreeysubtree|aiaj|+|bibj||a1ia1j||b1ib1j|

也就是维护 四个 绝对值求和。

然后这玩意可以分治计算,具体来说,对于 ans[1,n] (此处为值域)来说,可以分为两点都在中点的同一侧,和两点在中点的异侧,即:

ans[1,n]=ans[1,mid]+ans[mid+1][n]+cnt[1,mid]×sum[mid+1][n]cnt[mid+1][n]×sum[1,mid]

直接用线段树维护即可,合并时直接合并,不过需要改写 pushup 函数。

时间复杂度 O(nlogn) ,但是常数巨大。

听wkh讲可以学习一下 FHQtreap 的有交集合并,对于这道题也是 O(nlogn) 的,但是为什么他是最优解啊。

/*
GGrun
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
#define mk make_pair
#define ps push_back
#define fi first
#define se second
const int N=5e5+10,inf=0x3f3f3f3f;
const ll linf=0x3f3f3f3f3f3f3f3f,mod=1e9+7;
inline int read(){
char c=getchar();int x=0;
while(!isdigit(c))c=getchar();
while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x;
}
// #define int ll
int n,cnt,hd[N],rt[N][4],son[(N<<5)][2],ans[N<<5],dfn[N],ys[N],ed[N],fa[N],tot;
int bb[4][N],n1[4];
int sz[(N<<5)];
int sum[(N<<5)];
//rt 0-> a 1-> b 2-> a1 3-> b1
ll a[N],b[N],a1[N],b1[N],pre[N][4];
struct jj{
int to,next;
}bi[N<<1];
int cntp;
// int q[N<<5],top;
#define mo(x) (x>=mod?x-=mod:0)
inline void add(int x,int y){bi[++cntp]={y,hd[x]},hd[x]=cntp,bi[++cntp]={x,hd[y]},hd[y]=cntp;}
inline void add(int &k,int l,int r,int pos,int v){
k?k:k=++cnt;
++sz[k],sum[k]+=v;mo(sum[k]);
if(l==r)return;
int mid=l+r>>1;
pos<=mid?add(son[k][0],l,mid,pos,v):add(son[k][1],mid+1,r,pos,v);
ans[k]=(((ll)ans[son[k][0]]+ans[son[k][1]]+(ll)sz[son[k][0]]*sum[son[k][1]]-(ll)sz[son[k][1]]*sum[son[k][0]])%mod+mod)%mod;
}
inline int he(int x,int y){
if(!x||!y)return x^y;
sz[x]+=sz[y];sum[x]+=sum[y];
mo(sum[x]);
son[x][0]=he(son[x][0],son[y][0]);
son[x][1]=he(son[x][1],son[y][1]);
ans[x]=(((ll)ans[son[x][0]]+ans[son[x][1]]+(ll)sz[son[x][0]]*sum[son[x][1]]-(ll)sz[son[x][1]]*sum[son[x][0]])%mod+mod)%mod;
// q[++top]=y;
// son[y][0]=son[y][1]=sz[y]=sum[y]=ans[y]=0;
return x;
}
inline void dfs(int x,int f){
fa[x]=f;dfn[x]=++tot;ys[tot]=x;
for(int i=hd[x];i;i=bi[i].next){
int j=bi[i].to;
if(j!=f)
dfs(j,x);
}
ed[x]=tot;
}
ll anss[N];
int id;
inline void dfs(int x){
if(id==0)add(rt[x][0],1,n1[0],a[x],bb[0][a[x]]%mod);
else if(id==1)add(rt[x][1],1,n1[1],b[x],bb[1][b[x]]%mod);
else if(id==2)add(rt[x][2],1,n1[2],a1[x],bb[2][a1[x]]%mod);
else add(rt[x][3],1,n1[3],b1[x],(bb[3][b1[x]]%mod+mod)%mod);
for(int i=hd[x];i;i=bi[i].next){
int j=bi[i].to;
if(j!=fa[x]){
dfs(j);
rt[x][id]=he(rt[x][id],rt[j][id]);
}
}
(anss[x]+=(id<=1?1:-1)*ans[rt[x][id]])%=mod;
}
signed main(){
// #ifndef ONLINE_JUDGE
freopen("distance.in","r",stdin);
freopen("distance.out","w",stdout);
// #endif
// double t=clock();
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
n=read();
for(int i=1;i<n;++i)
add(read(),read());
dfs(1,0);
for(int i=1;i<=n;++i){
pre[dfn[i]][0]=a[i]=read(),pre[dfn[i]][1]=b[i]=read(),pre[dfn[i]][2]=a1[i]=(a[i]+b[i]),pre[dfn[i]][3]=b1[i]=(a[i]-b[i]);
pre[dfn[i]][0]=a[i]=(a[i]<<1),pre[dfn[i]][1]=b[i]=(b[i]<<1);
mo(pre[dfn[i]][0]);mo(pre[dfn[i]][1]);
bb[0][i]=a[i],bb[1][i]=b[i],bb[2][i]=a1[i],bb[3][i]=b1[i];
}
for(int i=0;i<4;++i){
sort(bb[i]+1,bb[i]+1+n),n1[i]=unique(bb[i]+1,bb[i]+1+n)-bb[i]-1;
}
for(int i=1;i<=n;++i){
a[i]=lower_bound(bb[0]+1,bb[0]+1+n1[0],a[i])-bb[0];
b[i]=lower_bound(bb[1]+1,bb[1]+1+n1[1],b[i])-bb[1];
a1[i]=lower_bound(bb[2]+1,bb[2]+1+n1[2],a1[i])-bb[2];
b1[i]=lower_bound(bb[3]+1,bb[3]+1+n1[3],b1[i])-bb[3];
}
for(int i=1;i<=n;++i){
pre[i][0]+=pre[i-1][0],pre[i][1]+=pre[i-1][1],pre[i][2]+=pre[i-1][2],pre[i][3]+=pre[i-1][3];
mo(pre[i][0]);mo(pre[i][1]);mo(pre[i][2]);pre[i][3]=(pre[i][3]%mod+mod)%mod;
}
id=0;
dfs(1);
for(int i=1;i<=cnt;++i)
son[i][0]=son[i][1]=sz[i]=sum[i]=ans[i]=0;
id=1;cnt=0;
dfs(1);
for(int i=1;i<=cnt;++i)
son[i][0]=son[i][1]=sz[i]=sum[i]=ans[i]=0;
id=2;cnt=0;
dfs(1);
for(int i=1;i<=cnt;++i)
son[i][0]=son[i][1]=sz[i]=sum[i]=ans[i]=0;
id=3;cnt=0;
dfs(1);
// cout<<(double(clock()-t))/1e6<<endl;
for(int i=1;i<=n;++i)
cout<<(anss[i]+mod)%mod<<'\n';
}

END   OF   T1

T2、P7671 疯狂动物城

其实就是把一堆板子套在一块了而已。(谁也不知道我调了一上午)

简要题意

1、对于 x 到 y 的路径上的点的权值都加上 v。

2、 给 x 、y ,求 ixyj=1disi,yai×j

3、将树恢复到第 x 次 1 操作之后的版本。

简单来说,树剖,区间加,区间求和,主席树。

那么首先树剖非常简单,先把他剖完就行了。然后主要是 线段树怎么维护信息。

这个式子显然可以拆:

j=1disi,yai×j=ai×(1+j)j2

我们设 kl,kr 为线段树上编号为k的节点所管的区间, sumk=i=klkrai×(1+j)j2
j 是什么?因为每次问的 y 是不固定的,我们必须得选出一个点来作为 y ,然后通过 y 的答案转移到 y 上,那么这个点任意,我们就选用 1 为 y

怎么转?

首先对于一个询问,他大概长这样:(画的很丑)

image

如果我们直接以 dep 值为到 y 的 距离 dis 的话,标出来就是这样:

image

但是对于这个询问,他真实的 disi,y 应该是这样的:

image

我们对比两张图,然后考虑怎么去转化答案。

然后你可以发现把 x 到 y 这一段分为 x 到 lca 和 lca 到 y 这两段是比较好转的。

首先对于从 xlca 这一段,无论是我们假定的 disi,还是真正的 disi,y,在这一段上都是一个公差为 1 的 单调递增数列,那么就将每个点的 dis 加上 ideplca 即可, i 此时等于 depydeplca 。即,对于 xlca 上的每个点的 dis 加一个定值。

对于答案来说,将求和式再次展开合并:

ans[x,lca]=ixlcaai×(disi+1)disi2=ixlcaai×disi22+ai×disi2=iai×disi22+iai×disi2=iai×(disi+j)22+iai×disi+j2=iai×disi22+iai×disi2+j+j22×iai+j×iai×disi

至此,我们只要在线段树中维护好 iaiiai×disiiai×disi22+iai×disi2 即可解决 从 x 到 lca 这段路上的 ans。

对于 lca 到 y 这段路上的点,他们的 dis 是单调递增的 ,但是 dis 是单调递减的,不过好在他们的 公差的绝对值都是 1。如果将 dis 转为 dis 就和上面的情况相同了。 如果是 dis 的话。

ans=iai×disi22iai×disi2=iai×disi22+iai×disi2iai×disi

非常好,用我们已经维护的信息都可以知道。

转化问题说完了,我们来看看线段树怎么维护信息。

首先我们要明确我们得维护哪些信息:

1、ansk=iai×disi22+iai×disi2

2、sumak=iai

3、 sumsak=iai×disi

为了进行区间加操作,我们还得维护一些别的:

4、 sumsk=idisi

5、 sumk=idisi22+idisi2

6、 tagk

因为是主席树,我们不太好进行 push_down 操作,所以我们采取标记永久化,在询问以及 push_up 的时候加上 tagk 的贡献。

然后还有一个防止炸空间的 trick ,因为是树剖,所以一次加操作会被我们分成 不多于 log 次区间加操作,然后我们用的还是主席树,他每进行一次区间加操作都会涨不到 log2 的空间,然而我们用主席树只是为了区分版本,每个版本实际上只用了本版本的 log 次 加操作的最后一次操作之后剩下的哪个版本,有不少版本是被浪费的,所以在写主席树时不一定每到一个点就新建,如果版本一样的话就可以不用新建,就像动态开点那样。

inline void add(int &k,int l,int r,int L,int R,ll v){
if(tim[k]!=ti){
ans[++cnt]=ans[k],sum[cnt]=sum[k],sums[cnt]=sums[k],tag[cnt]=tag[k],sumsa[cnt]=sumsa[k],suma[cnt]=suma[k],son[cnt][0]=son[k][0],son[cnt][1]=son[k][1],tim[cnt]=ti,k=cnt;
}
if(L<=l&&r<=R)return (void)(ans[k]+=(ll)sum[k]*v%mod,mo(ans[k]),tag[k]+=v,mo(tag[k]),sumsa[k]+=(ll)sums[k]*v%mod,mo(sumsa[k]),suma[k]+=v*(r-l+1)%mod,mo(suma[k]));
int mid=l+r>>1;
if(L<=mid)add(son[k][0],l,mid,L,R,v);
if(R>mid)add(son[k][1],mid+1,r,L,R,v);
ans[k]=((ll)ans[son[k][0]]+ans[son[k][1]]+(ll)sum[k]*tag[k]%mod)%mod;sumsa[k]=((ll)sumsa[son[k][0]]+sumsa[son[k][1]]+(ll)sums[k]*tag[k]%mod)%mod;suma[k]=((ll)suma[son[k][0]]+suma[son[k][1]]+(ll)tag[k]*(r-l+1)%mod)%mod;
}
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
#define mk make_pair
#define ps push_back
#define fi first
#define se second
const int N=1e6+10,inf=0x3f3f3f3f;
const ll linf=0x3f3f3f3f3f3f3f3f,mod=20160501,ny=10080251;
inline ll read(){
char c=getchar_unlocked();ll x=0,f=1;
while(!isdigit(c))f=c=='-'?-1:1,c=getchar_unlocked();
while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar_unlocked();
return x*f;
}
// #define int ll
#define mo(x) (x>=mod?x-=mod:0)
struct jp{
int to,next;
}bi[N];
int ti=0;
int ans[N<<4],tag[N<<4],sumsa[N<<4],cnt,suma[N<<4],cntp,laans,sum[N<<4],sums[N<<4];
// sum-> s/2+s*s/2 ans-> ans tag-> tag of suma suma-> sum of a_i sums-> sum of s_i sumsa-> sum of s_i*a_i
int son[N<<4][2],sz[N],rt[N],dep[N],hd[N],fa[N],s[N],dfn[N],now,tim[N<<4];
inline void add(int x,int y){bi[++cntp]={y,hd[x]},hd[x]=cntp,bi[++cntp]={x,hd[y]},hd[y]=cntp;}
inline void add(int &k,int l,int r,int L,int R,ll v){
if(tim[k]!=ti){
// cout<<l<<' '<<r<<endl;
ans[++cnt]=ans[k],sum[cnt]=sum[k],sums[cnt]=sums[k],tag[cnt]=tag[k],sumsa[cnt]=sumsa[k],suma[cnt]=suma[k],son[cnt][0]=son[k][0],son[cnt][1]=son[k][1],tim[cnt]=ti,k=cnt;
}
if(L<=l&&r<=R)return (void)(ans[k]+=(ll)sum[k]*v%mod,mo(ans[k]),tag[k]+=v,mo(tag[k]),sumsa[k]+=(ll)sums[k]*v%mod,mo(sumsa[k]),suma[k]+=v*(r-l+1)%mod,mo(suma[k]));
int mid=l+r>>1;
if(L<=mid)add(son[k][0],l,mid,L,R,v);
if(R>mid)add(son[k][1],mid+1,r,L,R,v);
ans[k]=((ll)ans[son[k][0]]+ans[son[k][1]]+(ll)sum[k]*tag[k]%mod)%mod;sumsa[k]=((ll)sumsa[son[k][0]]+sumsa[son[k][1]]+(ll)sums[k]*tag[k]%mod)%mod;suma[k]=((ll)suma[son[k][0]]+suma[son[k][1]]+(ll)tag[k]*(r-l+1)%mod)%mod;
// cout<<k<<' '<<l<<' '<<r<<' '<<L<<' '<<R<<' '<<suma[k]<<"ADD\n";
}
struct jj{
ll ans,suma,sumsa;
inline void operator +=(const jj &x){ans+=x.ans,suma+=x.suma,sumsa+=x.sumsa,mo(ans),mo(suma),mo(sumsa);}
};
inline jj ask(int k,int l,int r,int L,int R,ll ta=0){
if(L<=l&&r<=R){
return {((ll)ans[k]+(ll)sum[k]*ta%mod)%mod,((ll)suma[k]+(ll)ta*(r-l+1)%mod)%mod,((ll)sumsa[k]+(ll)sums[k]*ta%mod)%mod};
}
int mid=l+r>>1;jj ans={0,0,0};
if(L<=mid)ans+=ask(son[k][0],l,mid,L,R,(ta+tag[k])%mod);
if(R>mid)ans+=ask(son[k][1],mid+1,r,L,R,(ta+tag[k])%mod);
return ans;
}
inline void dfs(int x,int f){
dep[x]=dep[f]+1;fa[x]=f;sz[x]=1;
for(int i=hd[x];i;i=bi[i].next){
int j=bi[i].to;
if(j!=f){
dfs(j,x);
sz[x]+=sz[j];
if(sz[j]>sz[s[x]])s[x]=j;
}
}
}
int top[N],tot,n,m,a[N],ys[N];
inline void dfs2(int x,int zu){
top[x]=zu;dfn[x]=++tot;ys[tot]=x;
if(s[x])dfs2(s[x],zu);
for(int i=hd[x];i;i=bi[i].next){
int j=bi[i].to;
if(!dfn[j])dfs2(j,j);
}
}
inline void jian(int &k,int l,int r){
k=++cnt;
if(l==r){
sum[k]=(((ll)dep[ys[l]]+(ll)dep[ys[l]]*dep[ys[l]])/2ll)%mod,sums[k]=dep[ys[l]],suma[k]=a[l],sumsa[k]=(ll)a[l]*dep[ys[l]]%mod,ans[k]=(ll)sum[k]*a[l]%mod;
return;
}
int mid=l+r>>1;
jian(son[k][0],l,mid),jian(son[k][1],mid+1,r);
sum[k]=sum[son[k][0]]+sum[son[k][1]],sums[k]=sums[son[k][0]]+sums[son[k][1]],suma[k]=suma[son[k][0]]+suma[son[k][1]],sumsa[k]=sumsa[son[k][0]]+sumsa[son[k][1]],ans[k]=ans[son[k][0]]+ans[son[k][1]];
mo(sum[k]),mo(sums[k]),mo(suma[k]),mo(sumsa[k]),mo(ans[k]);
}
inline void add(int x,int y,ll z){
// int num=0;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
add(rt[ti],1,n,dfn[top[x]],dfn[x],z);
x=fa[top[x]];
}
if(dep[x]>dep[y])swap(x,y);
add(rt[ti],1,n,dfn[x],dfn[y],z);
}
inline ll ask(int x,int y){
jj ans1={0,0,0},ans2={0,0,0},zan;
int dep2=dep[y];
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]){
zan=ask(rt[now],1,n,dfn[top[y]],dfn[y],0);
ans2+=zan;
y=fa[top[y]];
}
else{
ans1+=ask(rt[now],1,n,dfn[top[x]],dfn[x],0);
x=fa[top[x]];
}
}
if(dep[x]<dep[y])ans2+=ask(rt[now],1,n,dfn[x],dfn[y]);
else ans1+=ask(rt[now],1,n,dfn[y],dfn[x]);
int lca=dep[x]<dep[y]?x:y;
ll ta=dep2-dep[lca]-dep[lca];
ans1.ans=((ans1.ans+ans1.suma*ta%mod*ta%mod*ny%mod+ans1.suma*ta%mod*ny%mod+ans1.sumsa*ta%mod)%mod+mod)%mod;
ta=dep2;
ans2.ans=(ans2.ans-ans2.sumsa+mod)%mod;
ans2.sumsa=(mod-ans2.sumsa)%mod;
ans2.ans=(ans2.ans+ans2.suma*ta%mod*ny%mod+ans2.sumsa*ta%mod+ans2.suma*ta%mod*ta%mod*ny%mod)%mod;
return ((ans1.ans+ans2.ans)%mod+mod)%mod;
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("in.in","r",stdin);
freopen("out.out","w",stdout);
#endif
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
n=read(),m=read();
for(int i=1;i<n;++i)
add(read(),read());
dfs(1,0);dfs2(1,1);
for(int i=1;i<=n;++i)
a[dfn[i]]=read();
jian(rt[ti],1,n);
now=ti;
// int na=0;
for(int i=1,x,y,op;i<=m;++i){
op=read();
// laans=0;
// na =cnt;
if(op==1)x=read()^laans,y=read()^laans,rt[ti+1]=rt[now],++ti,add(x,y,read()%mod),now=ti;
else if(op==2){
x=read()^laans,y=read()^laans,laans=ask(x,y);
cout<<laans<<'\n';
}
else now=read()^laans;
}
}

END   OF   T2

T3 、 区间 && 比赛(P8868 NOIP2022)

先说一下CSP模拟赛里的一道T4。

区间

image

题意比较清楚,就是对于每一个询问 l,r ,找出他所有的子区间 li,ri ,满足 liriAliA[li+1,ri1]BriA[li,ri1]

考虑把后两个条件分开来看。对于第一个条件,对于同一个左端点 li ,右端点越靠右,区间最大值越大,也就是他有单调性,所以我们可以用单调栈维护。具体来说,维护一个单调严格递减的栈,只有栈里的点才符合第一个要求,那么接下来要去判第二个要求了。假设目前枚举到了点 k ,因为第一个要求保证了此时栈里的点的 A 值是 lik 的最大值,那么去和 Bk 比较时,就直接用栈里的点的 A 值比较即可,也就是说对于栈里的所有点 i ,只要 Ai>Bk ,那么这个点就是合法的,而且因为他在单调栈里面,我们可以直接二分查找。所以我们直接把询问离散化下来,按照右端点排序,然后扫过去并记录区间历史和即可。

但是区间历史和怎么记呢?对于每一个右端点,我们可以在总共 O(nlogn) 的时间复杂度内找到哪些点是合法的,但是这些点并不是连续的,我要把他们的贡献算上去的话每次会被卡到 O(n) 的,那时间复杂度就被卡成 O(n2) 的了。

那么考虑一下为什么这些点不是一段连续的区间呢?因为中间有些点被 pop 出去了呗。 听起来像是在说废话,但是这就是本题的突破口,因为一个点只会被 poppush 一次,这也就保证了单调栈的时间复杂度,那么 push 表示他符合第一个条件, pop 就表示他已经不符合第一个条件了,而且永远也不会再符合条件了,那么我们可以直接给这些点打上一个标记,表示这个点已经不合法了,然后因为对于第二个条件我们是二分查找的单调栈里的一段区间,贡献就是在单调栈里区间加,也就相当于对于单调栈里合法的最小的下标 i ,和当前点 k ,所形成的区间 [i,k] 上直接区间加,然后删去其中不合法的点的贡献即可,那么这个删去不合法的贡献的过程可以通过刚才打的标记(不是懒惰标记,别搞混了)直接在线段树区间加的过程中实现。具体来说,对于一段区间 [l,r] ,对他进行一次区间加,原本贡献应是 rl+1 ,而实际贡献应是 rl+1tagktagk 表示 [l,r] 这段区间被标记的点的个数。

至此,本道题可以 O(nlog(n)) 的时间复杂度内解决。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define int ll
typedef pair<int,int> pii;
#define fi first
#define se second
#define mk make_pair
#define ps push_back
const int N=1e6+10,inf=0x3f3f3f3f;
const ll linf=0x3f3f3f3f3f3f3f3f,mod=1e9+7;
inline ll read(){
char c=getchar();ll x=0,f=1;
while(!isdigit(c))f=c=='-'?0:-1,c=getchar();
while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar();
return f?x:-x;
}
int n,Q,a[N],b[N];
struct jj{
int l,r,id;
}q[N];
ll sum[N<<2],tag[N<<2],c[N<<2],ta[N<<2];
inline void down(int k,int len1,int len2){
if(ta[k])sum[k<<1]+=ta[k]*(len1-tag[k<<1]),sum[k<<1|1]+=ta[k]*(len2-tag[k<<1|1]),ta[k<<1]+=ta[k],ta[k<<1|1]+=ta[k],ta[k]=0;
}
inline void add(int k,int l,int r,int pos){
if(l==r)return (void)(++tag[k]);
int mid=l+r>>1;
down(k,mid-l+1,r-mid);
pos<=mid?add(k<<1,l,mid,pos):add(k<<1|1,mid+1,r,pos);
tag[k]=tag[k<<1]+tag[k<<1|1];
}
inline void add(int k,int l,int r,int L,int R){
if(L<=l&&r<=R)return (void)(sum[k]+=r-l+1-tag[k],ta[k]++);
int mid=l+r>>1;
down(k,mid-l+1,r-mid);
if(L<=mid)add(k<<1,l,mid,L,R);
if(R>mid)add(k<<1|1,mid+1,r,L,R);
sum[k]=sum[k<<1]+sum[k<<1|1];
}
inline int ask(int k,int l,int r,int L,int R){
if(L<=l&&r<=R)return sum[k];
int mid=l+r>>1,ans=0;
down(k,mid-l+1,r-mid);
if(L<=mid)ans=ask(k<<1,l,mid,L,R);
if(R>mid)ans+=ask(k<<1|1,mid+1,r,L,R);
return ans;
}
int top;
pii st[N];
ll ans[N];
signed main(){
// #ifndef ONLINE_JUDGE
freopen("interval.in","r",stdin);
freopen("interval.out","w",stdout);
// #endif
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
n=read();
for(int i=1;i<=n;++i)
a[i]=read();
for(int i=2;i<=n;++i)
b[i]=read();
Q=read();
for(int i=1;i<=Q;++i){
q[i].l=read(),q[i].r=read();q[i].id=i;
}
sort(q+1,q+1+Q,[](const jj&x,const jj&y){return x.r<y.r;});
for(int i=1,j=1;i<=n;++i){
int l=1,r=top,pos=0;
while(l<=r){
int mid=l+r>>1;
if(st[mid].se<b[i])pos=mid,r=mid-1;
else l=mid+1;
}
// cout<<i<<' '<<pos<<' '<<st[pos].fi<<endl;
if(pos&&st[pos].fi<i)add(1,1,n,st[pos].fi,i-1);
while(j<=Q&&q[j].r==i){
ans[q[j].id]=ask(1,1,n,q[j].l,i),++j;
}
while(top&&st[top].se<=a[i])add(1,1,n,st[top].fi),--top;
st[++top]={i,a[i]};
}
for(int i=1;i<=Q;++i)
cout<<ans[i]<<'\n';
return 0;
}

好的,刚才我们提到了 区间历史和 ,如果你学过的话,那么上面这道题就是一个板子。接下来再介绍一道区间历史和的例题吧,其实和这道题很像,只不过线段树难写了点。

比赛

题意仍然是给一堆询问 [l,r] ,问他的所有子区间 [li,ri] ,求 maxk[li,ri]Ak×maxk[li,ri]Bk

又是对于一堆区间他的子区间的所有贡献的题,我们自然可以离线下来然后扫描线扫过去。接下来想想怎么维护 max 操作。仍然是单调栈,像取 max,min 这种有单调性的操作直接上单调栈,进行区间覆盖操作。具体来讲,维护一个严不严格都行的单调递减栈,那么每次 pop 完后栈顶元素 + 1 到 目前枚举点 i 这段区间的 A/B 的最大值都应是 Ai/Bi ,然后直接进行区间赋值。每次枚举完一个右端点后,就对整个区间的历史版本加一,说人话就是让他们的答案都加上目前自己的 Aj×Bj

那么直接就来到了线段树上,所以接下来讲讲怎么维护这颗线段树。

当前扫描线扫到了右端点 r ,对于树上一个节点 k ,他管的区间是 lk,rk ,首先我们需要历史答案和 ansk ,为了维护 ansk ,我们得知道目前的 j=lkrkmaxp=jrAp×maxp=jrBp (以下简称 AB ),那么为了维护 AB ,我们还得维护一些东西,因为我们每次是分开去给A和B赋值,所以当给 A 赋值时 AB=A×B ,所以我们还需要维护 B ,同理,我们还需要维护 A

此时我们已经维护了 ABABans 这些已经达到了信息闭环了(信息闭环就是说我已经可以用已知的信息维护已知的信息了,是线段树维护信息是否可行的重要标志)。但因为是区间加,我们还需要维护 懒惰标记。首先需要 tagAtagBtagC 分别表示 A区间赋值、B区间赋值 和 答案求和次数的懒标。接下来考虑怎么维护他们。

一眼看上去是不是很好维护,直接就在add函数里修改就行。但其实你交上去就会发现WA了。为什么?你百思不得其解,对着大样例调了一组又一组,然后发现这些 tag 之间会产生时间上的冲突,举个例子:一先对一段区间 [l,r] 进行了 A 赋值操作,然后对他进行一次求和,然后又对他进行了一次 A 赋值操作,然后在进行一次求和操作,然后询问他的一个真子区间,此时你需要进行 push_down 操作,在 push_down 里面,你需要同时将三个 tag 下传下去,先下传哪个就有了争议,似乎先下传哪个都不对,就像这个例子,因为这两个求和操作所加的 AB 的值并不相同,无论谁先下传都不对。 如果你在这里疑惑了,建议先做一下 P3373 【模板】线段树 2】 这道题比较简单,但它里面处理了 tagtag 的冲突,很有益于启发这道题的 tag 的冲突解决。 好,类比这道题的处理方式,我们去解决这个冲突。冲突在于 tagAtagBtagC 无法维护谁先谁后,那么我们尝试通过钦定一个在前并改写他的方式解决冲突。显然赋值操作无法被改写,所以我们只能尝试去改写 tagC

首先明确 tagC 表示的是 求和次数的懒标 , 或者说 是 AB 的求和次数的懒标,那么对于有 tagAtagB 的情况,我们相当于已知当前 AB 了,直接把 AB 的懒标当成 常数的懒标即可,此处用 tagc 表示,然后对于只有 tagA 的情况,我们可以将 AB 的懒标当成 B 的懒标,同理对于只有 tagB 的情况,将 AB 的懒标当成 A 的懒标,分别用 taga,tagb 表示,同时将上面用到的 tagC 更改为 tagab,那么我们现在已经维护了 tagab,taga,tagb,tagc,tagA,tagB 六种懒标,接下来看看他们有没有达到信息闭环的要求呢? 当标记下传时,我们钦定了先下传 tagab 这样的求和标记,那么他们可以通过是否有 tagAtagB 在四个求和标记里面转化,最后转到 tagc 里就不会再转化了,只需要乘上当前区间的 len 就是他的贡献了,所以对于这六个已经达到信息闭环了,所以至此我们的所有信息都已经达到了信息闭环,可以直接上线段树了。

至此本题还没有结束。

这题的线段树写起来感觉有点狗屎,在此推荐另外一种写法。可看博客
数据结构闲谈:范围分治的「双半群」模型

我对于他的理解就是,可以把所有修改操作分为三件事 其中两件 标记对标记的作用标记对信息的作用 对应着 push_down 和 add 函数中对信息的直接修改,还有一件 信息与信息的合并 对应着 push_up 。其实就相当于普通线段树中写的 add_tag 函数。这样写起来会方便很多。

要学习线段树的话,可以去看看 线段树进阶 Part 1 ,这篇写的非常好,看着也舒服。

如果对于本题你真的还不会怎么合并信息、标记的话
// 标记作用于标记 jiaa -> tagA jiab -> tagB
inline void operator +=(const tag&x){
if(jiaa&&jiab)tagc+=x.tagab*jiaa*jiab+x.taga*jiaa+x.tagb*jiab+x.tagc;
else if(jiaa)tagc+=x.taga*jiaa+x.tagc,tagb+=x.tagb+x.tagab*jiaa;
else if(jiab)tagc+=x.tagb*jiab+x.tagc,taga+=x.taga+x.tagab*jiab;
else tagab+=x.tagab,taga+=x.taga,tagb+=x.tagb,tagc+=x.tagc;
if(x.jiaa)jiaa=x.jiaa;
if(x.jiab)jiab=x.jiab;
}
// 标记作用于信息 ab -> sumab a-> suma b-> sumb
inline void operator +=(const tag&x){
ans+=x.tagab*ab+x.taga*a+x.tagb*b+x.tagc*len;
if(x.jiaa&&x.jiab)ab=x.jiaa*x.jiab*len,a=x.jiaa*len,b=x.jiab*len;
else if(x.jiaa)ab=x.jiaa*b,a=x.jiaa*len;
else if(x.jiab)ab=x.jiab*a,b=x.jiab*len;
}
// 信息作用于信息
inline void operator +=(const jj&x){
ans+=x.ans,ab+=x.ab,a+=x.a,b+=x.b,len+=x.len;
}
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mk make_pair
#define ps push_back
const int N=1e6+10,inf=0x3f3f3f3f;
const ll linf=0x3f3f3f3f3f3f3f3f,mod=1e9+7;
inline int read(){
char c=getchar_unlocked();int x=0;
while(!isdigit(c))c=getchar_unlocked();
while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar_unlocked();
return x;
}
int n;
ull a[N],b[N];
struct tag{
ull tagab,taga,tagb,tagc,jiaa,jiab;
inline void operator +=(const tag&x){
if(jiaa&&jiab)tagc+=x.tagab*jiaa*jiab+x.taga*jiaa+x.tagb*jiab+x.tagc;
else if(jiaa)tagc+=x.taga*jiaa+x.tagc,tagb+=x.tagb+x.tagab*jiaa;
else if(jiab)tagc+=x.tagb*jiab+x.tagc,taga+=x.taga+x.tagab*jiab;
else tagab+=x.tagab,taga+=x.taga,tagb+=x.tagb,tagc+=x.tagc;
if(x.jiaa)jiaa=x.jiaa;
if(x.jiab)jiab=x.jiab;
}
}ta[N];
struct jj{
ull ans,ab,a,b,len;
inline void operator +=(const tag&x){
ans+=x.tagab*ab+x.taga*a+x.tagb*b+x.tagc*len;
if(x.jiaa&&x.jiab)ab=x.jiaa*x.jiab*len,a=x.jiaa*len,b=x.jiab*len;
else if(x.jiaa)ab=x.jiaa*b,a=x.jiaa*len;
else if(x.jiab)ab=x.jiab*a,b=x.jiab*len;
}
inline void operator +=(const jj&x){
ans+=x.ans,ab+=x.ab,a+=x.a,b+=x.b,len+=x.len;
}
}tr[N];
inline void down(int k){
if(!ta[k].tagab&&!ta[k].taga&&!ta[k].tagb&&!ta[k].tagc&&!ta[k].jiaa&&!ta[k].jiab)return;
tr[k<<1]+=ta[k],tr[k<<1|1]+=ta[k],ta[k<<1]+=ta[k],ta[k<<1|1]+=ta[k];ta[k]={0,0,0,0,0,0};
}
inline void add(int k,int l,int r,int L,int R,ull v,int op){
if(L<=l&&r<=R){
tag ji;
if(!op)ji={1,0,0,0,0,0};
else ji={0,0,0,0,(op==1?v:0),(op==2?v:0)};
tr[k]+=ji,ta[k]+=ji;
// cout<<k<<' '<<l<<' '<<r<<' '<<L<<' '<<R<<' '<<v<<' '<<op<<" "<<tr[k].ans<<' '<<tr[k].a<<' '<<tr[k].b<<endl;
return;
}
down(k);
int mid=l+r>>1;
if(L<=mid)add(k<<1,l,mid,L,R,v,op);
if(R>mid)add(k<<1|1,mid+1,r,L,R,v,op);
tr[k]=tr[k<<1];tr[k]+=tr[k<<1|1];
}
inline ull ask(int k,int l,int r,int L,int R){
if(L<=l&&r<=R)return tr[k].ans;
down(k);
int mid=l+r>>1;ull ans=0;
if(L<=mid)ans+=ask(k<<1,l,mid,L,R);
if(R>mid)ans+=ask(k<<1|1,mid+1,r,L,R);
return ans;
}
inline void jian(int k,int l,int r){
tr[k].len=r-l+1;
if(l==r)return;
int mid=l+r>>1;
jian(k<<1,l,mid),jian(k<<1|1,mid+1,r);
}
struct ak{
int l,r,id;
}q[N];
int q1[N],top1,q2[N],top2;
ull ans[N];
signed main(){
#ifndef ONLINE_JUDGE
freopen("in.in","r",stdin);
freopen("out.out","w",stdout);
#endif
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
n=read(),n=read();
for(int i=1;i<=n;++i)
a[i]=read();
for(int i=1;i<=n;++i)
b[i]=read();
jian(1,1,n);
int Q=read();
for(int i=1;i<=Q;++i){
q[i].l=read(),q[i].r=read(),q[i].id=i;
}
sort(q+1,q+1+Q,[](const ak&x,const ak&y){return x.r<y.r;});
a[0]=b[0]=1e14;
for(int i=1,j=1;i<=n;++i){
while(a[q1[top1]]<=a[i])--top1;
while(b[q2[top2]]<=b[i])--top2;
add(1,1,n,q1[top1]+1,i,a[i],1);
add(1,1,n,q2[top2]+1,i,b[i],2);
add(1,1,n,1,i,0,0);
// for(int j=1;j<=(n<<1);++j)
// down(j);
// cout<<q1[top1]+1<<' '<<i<<' '<<a[i]<<" ji111111111\n";
// cout<<q2[top2]+1<<' '<<i<<' '<<b[i]<<" ji222222222\n";
while(j<=Q&&q[j].r==i)
ans[q[j].id]=ask(1,1,n,q[j].l,i),++j;
// if(i==2)return 0;
q1[++top1]=i,q2[++top2]=i;
}
for(int i=1;i<=Q;++i)
cout<<ans[i]<<'\n';
return 0;
}

END    OF    T3

T4 、 倒水(多校A层冲刺NOIP2024模拟赛17 T4)

非常吊的一道题。

首先手膜一下倒水的过程:

一开始肯定是从第一杯里往后面的杯子 x 倒水:

image

然后中间那几个没水的杯子不用管,接下来来到了你刚到水的杯子 x ,然后他去倒水:

假设他还是往后倒,倒给了杯子 y

image

这个过程中和 杯子 1 给 杯子 x 倒水是一样的。

如果他往前倒:

image

那么可以发现后面的杯子一定都没有水了,所以过程就此结束。

总结上面的过程:

从杯子 1 开始向右倒水,那么两个杯子之间不用管,下次倒水一定发生在刚才被倒水的那个杯子中,然后分为继续向右倒和向左倒两种情况。继续向右倒的话,就和一开始的过程一样,向左倒的话倒水过程就此结束。

:1

画成图就是:

image

fi,k 表示第 i 个杯子目前有 k 的水的概率, ai 表示杯子 i 的容量。

从前往后倒的时候好说,从 fi,k 转移到了 fj,min(k,aj) 。但是向前转移的时候好像还得考虑前面的杯子中有多少水才行,这就成 O(n4) 的了。但是这道题很特殊,整个倒水过程只会有一次向前倒水的过程,我们想一下这次到谁会到给谁: 如果倒给了一个空杯子,那么也就是倒出去了 min(k,aj) 的水。如果是一个已经有过水的杯子,因为整个倒水过程很单一,我目前这个杯子 i 里面的水一定是从杯子 j 里倒出来的,所以我往回倒的时候一定会把水全倒回去,也就是倒 k ,因为这个时候 k 一定 小于等于 aj ,所以无论前面的杯子有没有倒过水我都是 倒回去了 min(k,aj) 的水。

这样的话就可以 O(n3) 去转移了。最后答案就是 k=1aifi,k×k

一个细节是:每次转移都需要乘上单次选择数的逆元,即 (n1)mod2

但是其实我们不用最后去用 f 数组去统计答案,我们想一下最终杯子里的水都是从哪来的:

一个杯子中被倒进来水之后一定还会倒出去一些,那么如果他能剩下一些水的话,这些剩下的水一定这个杯子的答案有贡献,那些被到出去的水也一定对被倒水的那个杯子的答案有贡献。

所以有贡献的水只有两种情况:

1、给别的杯子倒水时剩下的

2、被右边的杯子倒回来的

以上这两种转移可以直接加入答案中而不用再加入 f 中。

那么整个过程又可以再简化,我们已经不用往回转移了,那么我们稍微改一下 f 的定义:从左边转移过来的水的概率,整个过程分为了:

1、从左边向右边转移水:

转移到右边的 f 上
给自己留下的水直接计入自己的答案

2、从右边向左边转移水:

给左边的水直接算入他的答案
给自己留下的水直接计入自己的答案

因此我们可以写出代码:

ll ny=qpow(n-1,mod-2);
//初始化
f[1][a[1]]=1;
for(int i=1;i<=n;++i){
//右向左倒水
for(int j=1;j<i;++j){
//分情况记入 j 的答案和 i 的答案
for(int k=1;k<=min(a[j],a[i]);++k){
ans[j]+=f[i][k]*k%mod*ny%mod;mo(ans[j]);
}
for(int k=a[j]+1;k<=a[i];++k){
ans[j]+=f[i][k]*a[j]%mod*ny%mod;mo(ans[j]);
ans[i]+=f[i][k]*ny%mod*(k-a[j])%mod;mo(ans[i]);
}
}
// 左向右倒水
for(int j=i+1;j<=n;++j){
//分情况计入 j 的 f 数组和 i 的答案
for(int k=1;k<=min(a[i],a[j]);++k){
f[j][k]+=f[i][k]*ny%mod;mo(f[j][k]);
}
for(int k=a[j]+1;k<=a[i];++k){
f[j][a[j]]+=f[i][k]*ny%mod;mo(f[j][a[j]]);
ans[i]+=f[i][k]*ny%mod*(k-a[j])%mod;mo(ans[i]);
}
}
}

观察上面的转移,我们在确定 i,j 之后又枚举了一维 k 表示当前的水量,但实际上对于一个 j 我们只关心 ajk 的大小关系,所以我们就可以前缀和优化一下。我们分别来看一下这四种情况怎么优化:

1、左倒右, kaj

就是给 ansj 加上了 k=1ajfi,k×k×ny

2、左倒右, k>aj

ansjk=aj+1aifi,k×aj×ny

ansi(k=aj+1aifi,k×kk=aj+1aifi,k×aj)×ny

3、右倒左, kaj

就是原始式子,没什么可改写的。

4、右倒左, k>aj

fj,ajk=aj+1aifi,k×ny (注意到 f 数组只用了一个,所以可以省略一维,用 fj 表示 fj,aj

ansi(k=aj+1aifi,k×kk=aj+1aifi,k×aj)×ny (和上面2中的那个一样)

根据上面这些式子,我们需要对于每个 i 维护好 Fj=k=1jfi,kGj=k=1jfi,k×k 即可处理除了 3 之外的所有式子

一共 4 个式子我们已经搞了 3 个,就剩 3 中的那个我们无法优化。

那么我们换种思路,可以观察到 3 中的转移第二维 k 不变,所以从这方面入手,我们画图来说:

image

以此图来表示 fi,k

对于 3 操作来说, fi,k 会对 fj,k(j>i) 造成贡献 ,换句话说, fi,kfj,k(j<i) 转移而来,即图中绿色的地方由红色的地方转移而来。

image

所以用一个横向的前缀和 FF 即可。

可以写出代码:

//prea 表示 F pree 表示 横向前缀和
f[1]=1;
for(int i=1;i<=n;++i){
for(int j=1;j<=a[i];++j){
prea[j]=prea[j-1]+pree[j]*ny%mod;mo(prea[j]);
g[j]=g[j-1]+pree[j]*ny%mod*j%mod;mo(g[j]);
}
prea[a[i]]+=f[i];mo(prea[a[i]]);g[a[i]]+=f[i]*a[i]%mod;mo(g[a[i]]);
for(int j=1;j<i;++j){
ans[j]+=g[min(a[i],a[j])]*ny%mod;mo(ans[j]);
if(a[i]>a[j])ans[j]+=(prea[a[i]]-prea[a[j]]+mod)%mod*a[j]%mod*ny%mod;mo(ans[j]);
if(a[i]>a[j])ans[i]+=(g[a[i]]-g[a[j]]-(prea[a[i]]-prea[a[j]]+mod)%mod*a[j]%mod+mod*10)%mod*ny%mod;mo(ans[i]);
}
for(int j=i+1;j<=n;++j){
if(a[i]>a[j]){
f[j]+=(prea[a[i]]-prea[a[j]]+mod)%mod*ny%mod;mo(f[j]);
ans[i]+=(g[a[i]]-g[a[j]]-(prea[a[i]]-prea[a[j]]+mod)%mod*a[j]%mod+mod*10)%mod*ny%mod;mo(ans[i]);
}
}
for(int j=1;j<=a[i];++j){
pree[j]+=(prea[j]-prea[j-1]+mod)%mod;mo(pree[j]);
}
}

优化了这么多,我们已经可以 O(n2) 解决这个题了,但是还是过不去,我们继续通过画图分析各个转移的本质:

1、首先看看 ff 之间的转移

上面的 3 和 4 中的第一个 是 ff 之间的转移。

对于第 3 个转移已经通过上面那张图展现出来了,接下来看看 4 中的第一个:

image

就像上图一样。对于被绿色圈起来的那个地方,会被前面所有比他高的转移过来。

那么整个过程就可以用下图表示:

image

考虑怎么快速维护这个横向的前缀和,对于黄色箭头表示的横向转移,看看他的式子:

fi,k=FFi1,k×ny

FFi,k=FFi1,k+fi,k

但是后面这个式子可以这么写: FFi,k=FFi1,k×(ny+1)

那这是什么,这是区间乘。

那红色的那个是不是更好说了,区间查询,单点加。

接下来我们都只需要维护横向的前缀和就行了, Fi,k=j=1ifj,kGi,k=j=1ifj,k×k

所以直接用线段树维护 FG 即可

2、给别的杯子倒水时给自己留下的水

首先这种情况不用区分前后,我们直接看全局:

式子: fi,k×(k×nj=1nmin(k,aj))×ny

image

模拟一下中间有箭头的那列,我们先枚举 k

image

上面红色的表示 min(k,aj)

image

k 上升时整体会跟着上升

image

k 上升到一定程度时有一些列已经跟不上了。

这个过程仍然可以用线段树维护。

首先对于每个 k ,我们预处理出 min(k,aj)

对于一开始的式子: fi,k×(k×nj=1nmin(k,aj))×ny

把它分为

n×fi,k×kfi,k×min(k,aj)

前者就是 用 G 可求得 ,后者仍然可以用线段树来维护。

3、被别的杯子倒回来水

image

竟然和第一步一样,只需要在第一遍从左向右扫过去的时候记一下此时的 j=1aiGi,jj=ai+1nFi,j ,即可用最后全局的 GF 减去刚才记的东西就是后面黄色的和红色的部分。

到此为止 我们已经把一个 O(n3) 的暴力DP优化到了 O(nlog(n))

再仔细思考一下整个过程,其实就是带权矩形面积和。

闲话:

9G 听了 Qyun 的几句话之后说:“我会 O(n2) 的了,我现在感觉 豁然开朗!”。然后我说了句:“别急,你还得豁然开朗好几次呢。”

/*
GGrun
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
#define ps push_back
#define mk make_pair
#define fi first
#define se second
const int N=1e6+10,M=2e5+10,inf=0x3f3f3f3f;
const ll linf=0x3f3f3f3f3f3f3f3f,mod=998244353;
inline int read(){
char c=getchar();int x=0;
while(!isdigit(c))c=getchar();
while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x;
}
inline ll qpow(ll x,ll y){
ll ans=1;
while(y){
if(y&1)ans=ans*x%mod;
x=x*x%mod;y>>=1;
}
return ans;
}
#define mo(x) (x>=mod?x-=mod:0)
ll prsum[M],pre[M],preny[M];
int a[M],b[M],n;
struct jp{
ll tagc,tagj1,tagj2;
inline void operator +=(const jp&x){
tagc=tagc*x.tagc%mod;
tagj1=(tagj1+x.tagj1+x.tagc*tagj1)%mod;
tagj2=(tagj2+x.tagj2+x.tagc*tagj2)%mod;
}
}tag[N];
struct jj{
ll sum,f,g,ans;
inline void operator +=(const jp&x){
f=(f*x.tagc+x.tagj1)%mod;
g=(g*x.tagc+x.tagj2)%mod;
ans=(ans*x.tagc+x.tagj1*sum)%mod;
}
inline void operator +=(const jj&x){
f+=x.f;g+=x.g;ans+=x.ans;
mo(f),mo(g),mo(ans);
}
}tr[N];
inline void jian(int k,int l,int r){
tag[k].tagc=1;
if(l==r)return (void)(tr[k].sum=prsum[l]);
int mid=l+r>>1;
jian(k<<1,l,mid),jian(k<<1|1,mid+1,r);
tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}
inline void down(int k){
if(tag[k].tagc!=1||tag[k].tagj1||tag[k].tagj2){
tr[k<<1]+=tag[k],tr[k<<1|1]+=tag[k],tag[k<<1]+=tag[k],tag[k<<1|1]+=tag[k];
tag[k]={1,0,0};
}
}
inline void up(int k){
tr[k].f=tr[k<<1].f+tr[k<<1|1].f,tr[k].g=tr[k<<1].g+tr[k<<1|1].g,tr[k].ans=tr[k<<1].ans+tr[k<<1|1].ans;
mo(tr[k].f);mo(tr[k].g);mo(tr[k].ans);
}
inline void add(int k,int l,int r,int L,int R){
if(L<=l&&r<=R){
jp x={preny[1]+1,0,0};
tr[k]+=x,tag[k]+=x;
return;
}
down(k);
int mid=l+r>>1;
if(L<=mid)add(k<<1,l,mid,L,R);
if(R>mid)add(k<<1|1,mid+1,r,L,R);
up(k);
}
inline void add(int k,int l,int r,int pos,int v1,int v2){
if(l==r){jp x={1,v1,v2};tr[k]+=x,tag[k]+=x;return;}
down(k);
int mid=l+r>>1;
pos<=mid?add(k<<1,l,mid,pos,v1,v2):add(k<<1|1,mid+1,r,pos,v1,v2);
up(k);
}
inline jj ask(int k,int l,int r,int L,int R){
if(L<=l&&r<=R){
return tr[k];
}
down(k);
int mid=l+r>>1;
jj ans={0,0,0,0};
if(L<=mid)ans=ask(k<<1,l,mid,L,R);
if(R>mid)ans+=ask(k<<1|1,mid+1,r,L,R);
return ans;
}
ll ans[M],tpg[M],tpf[M];
int main(){
freopen("bottle.in","r",stdin);
freopen("bottle.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
n=read();
for(int i=1;i<=n;++i)
a[i]=read(),b[i]=a[i];
sort(b+1,b+1+n);
ll ny=qpow(n-1,mod-2);
pre[0]=preny[0]=1;
for(int i=1,j=1;i<=n;++i){
pre[i]=pre[i-1]*(n-1)%mod,preny[i]=preny[i-1]*ny%mod;
prsum[i]=prsum[i-1]+n-j+1;
mo(prsum[i]);
while(j<=n&&b[j]==i)++j;
}
jian(1,1,n);
add(1,1,n,a[1],1,(ll)a[1]%mod);
ans[1]=((ll)a[1]*n%mod-prsum[a[1]]+mod+ans[1])%mod*ny%mod;
jj op=ask(1,1,n,1,a[1]);
tpg[1]=op.g;tpf[1]=(tr[1].f-op.f+mod)%mod;
for(int i=2;i<=n;++i){
op=ask(1,1,n,1,a[i]);
op.f=op.f*ny%mod,op.g=op.g*ny%mod,op.ans=op.ans*ny%mod;
if(a[i]<n){
op.g+=(tr[1].f*ny-op.f+mod)%mod*a[i]%mod;
op.ans+=(tr[1].f*ny-op.f+mod)%mod*prsum[a[i]]%mod;
mo(op.g);mo(op.ans);
}
ans[i]+=(op.g*n-op.ans+mod)%mod*ny%mod;
ll zan=tr[1].f;
add(1,1,n,1,a[i]);
add(1,1,n,a[i],(zan*ny-op.f+mod)%mod,(zan*ny-op.f+mod)%mod*a[i]%mod);
op=ask(1,1,n,1,a[i]);
tpg[i]=op.g;tpf[i]=(tr[1].f-op.f+mod)%mod;
}
for(int i=1;i<=n;++i){
jj op=ask(1,1,n,1,a[i]);
op.g-=tpg[i];op.g=(op.g*ny%mod+mod)%mod;
ans[i]+=op.g;mo(ans[i]);
if(a[i]<n){
op.f=(tr[1].f-op.f+mod)%mod;
op.f=(op.f-tpf[i]+mod)%mod*ny%mod;
ans[i]+=op.f*a[i]%mod;mo(ans[i]);
}
}
for(int i=1;i<=n;++i)
cout<<(ans[i]+mod)%mod<<'\n';
}

END   OF   T4

posted @   lzrG23  阅读(129)  评论(17编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 上周热点回顾(2.17-2.23)
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
点击右上角即可分享
微信分享提示