G-数据结构-G
T1 距离
首先这题部分分很多,直接
那么正解几乎和部分分就没什么关系了。
首先看到
中带了一个
这个式子是显而易见的,那么通过这个式子我们就可以将
但是我们转了个dan啊,取
你说得对,但是,取
在原本问题中,一个特征值为
那么
学习切比雪夫距离可看这篇博客。
然后人类(反正不是我)发现 切比雪夫距离可以转化为曼哈顿距离 ,也就意味着可以将 取
怎么转呢?
我们通过图像来看,这是平面直角坐标系上所有与原点的切比雪夫距离为 1 的点:
一个边长为 2 的正方形的四条边。
然后我们在看看平面直角坐标系上所有与原点的曼哈顿距离为 1 的点:
一个边长为
既然都是正方形,那么我给他转一下,然后再将边长乘上
按理来说矩阵就是用来将一个坐标系旋转的,但是我根本不会好吧。
而且因为这是二维的,我们可以用虚数导一下。
所以将一个二维点进行旋转可以用虚数来导。那么将
当然你旋转 45°、135°、-45°、-135° 都可以,一般会把
接下来呢,设
也就是维护 四个 绝对值求和。
然后这玩意可以分治计算,具体来说,对于
直接用线段树维护即可,合并时直接合并,不过需要改写
时间复杂度
听wkh讲可以学习一下
码
/* 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'; }
T2、P7671 疯狂动物城
其实就是把一堆板子套在一块了而已。(谁也不知道我调了一上午)
简要题意:
1、对于 x 到 y 的路径上的点的权值都加上 v。
2、 给 x 、y ,求
3、将树恢复到第 x 次 1 操作之后的版本。
简单来说,树剖,区间加,区间求和,主席树。
那么首先树剖非常简单,先把他剖完就行了。然后主要是 线段树怎么维护信息。
这个式子显然可以拆:
我们设
j 是什么?因为每次问的
怎么转?
首先对于一个询问,他大概长这样:(画的很丑)
如果我们直接以 dep 值为到 y 的 距离
但是对于这个询问,他真实的
我们对比两张图,然后考虑怎么去转化答案。
然后你可以发现把 x 到 y 这一段分为 x 到 lca 和 lca 到 y 这两段是比较好转的。
首先对于从
对于答案来说,将求和式再次展开合并:
至此,我们只要在线段树中维护好
对于 lca 到 y 这段路上的点,他们的
非常好,用我们已经维护的信息都可以知道。
转化问题说完了,我们来看看线段树怎么维护信息。
首先我们要明确我们得维护哪些信息:
1、
2、
3、
为了进行区间加操作,我们还得维护一些别的:
4、
5、
6、
因为是主席树,我们不太好进行 push_down 操作,所以我们采取标记永久化,在询问以及 push_up 的时候加上
然后还有一个防止炸空间的 trick ,因为是树剖,所以一次加操作会被我们分成 不多于 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; } }
T3 、 区间 && 比赛(P8868 NOIP2022)
先说一下CSP模拟赛里的一道T4。
区间
题意比较清楚,就是对于每一个询问
考虑把后两个条件分开来看。对于第一个条件,对于同一个左端点
但是区间历史和怎么记呢?对于每一个右端点,我们可以在总共
那么考虑一下为什么这些点不是一段连续的区间呢?因为中间有些点被
至此,本道题可以
码
#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; }
好的,刚才我们提到了 区间历史和 ,如果你学过的话,那么上面这道题就是一个板子。接下来再介绍一道区间历史和的例题吧,其实和这道题很像,只不过线段树难写了点。
比赛
题意仍然是给一堆询问
又是对于一堆区间他的子区间的所有贡献的题,我们自然可以离线下来然后扫描线扫过去。接下来想想怎么维护
那么直接就来到了线段树上,所以接下来讲讲怎么维护这颗线段树。
当前扫描线扫到了右端点
此时我们已经维护了
一眼看上去是不是很好维护,直接就在add函数里修改就行。但其实你交上去就会发现WA了。为什么?你百思不得其解,对着大样例调了一组又一组,然后发现这些
首先明确
至此本题还没有结束。
这题的线段树写起来感觉有点狗屎,在此推荐另外一种写法。可看博客
数据结构闲谈:范围分治的「双半群」模型 。
我对于他的理解就是,可以把所有修改操作分为三件事 其中两件 标记对标记的作用 、 标记对信息的作用 对应着 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; }
T4 、 倒水(多校A层冲刺NOIP2024模拟赛17 T4)
非常吊的一道题。
首先手膜一下倒水的过程:
一开始肯定是从第一杯里往后面的杯子
然后中间那几个没水的杯子不用管,接下来来到了你刚到水的杯子
假设他还是往后倒,倒给了杯子
这个过程中和 杯子 1 给 杯子
如果他往前倒:
那么可以发现后面的杯子一定都没有水了,所以过程就此结束。
总结上面的过程:
从杯子 1 开始向右倒水,那么两个杯子之间不用管,下次倒水一定发生在刚才被倒水的那个杯子中,然后分为继续向右倒和向左倒两种情况。继续向右倒的话,就和一开始的过程一样,向左倒的话倒水过程就此结束。
画成图就是:
从前往后倒的时候好说,从
这样的话就可以
一个细节是:每次转移都需要乘上单次选择数的逆元,即
但是其实我们不用最后去用
一个杯子中被倒进来水之后一定还会倒出去一些,那么如果他能剩下一些水的话,这些剩下的水一定这个杯子的答案有贡献,那些被到出去的水也一定对被倒水的那个杯子的答案有贡献。
所以有贡献的水只有两种情况:
1、给别的杯子倒水时剩下的
2、被右边的杯子倒回来的
以上这两种转移可以直接加入答案中而不用再加入
那么整个过程又可以再简化,我们已经不用往回转移了,那么我们稍微改一下
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]); } } }
观察上面的转移,我们在确定
1、左倒右,
就是给
2、左倒右,
3、右倒左,
就是原始式子,没什么可改写的。
4、右倒左,
根据上面这些式子,我们需要对于每个
一共 4 个式子我们已经搞了 3 个,就剩 3 中的那个我们无法优化。
那么我们换种思路,可以观察到 3 中的转移第二维
以此图来表示
对于 3 操作来说,
所以用一个横向的前缀和
可以写出代码:
//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]); } }
优化了这么多,我们已经可以
1、首先看看
上面的 3 和 4 中的第一个 是
对于第 3 个转移已经通过上面那张图展现出来了,接下来看看 4 中的第一个:
就像上图一样。对于被绿色圈起来的那个地方,会被前面所有比他高的转移过来。
那么整个过程就可以用下图表示:
考虑怎么快速维护这个横向的前缀和,对于黄色箭头表示的横向转移,看看他的式子:
但是后面这个式子可以这么写:
那这是什么,这是区间乘。
那红色的那个是不是更好说了,区间查询,单点加。
接下来我们都只需要维护横向的前缀和就行了,
所以直接用线段树维护
2、给别的杯子倒水时给自己留下的水
首先这种情况不用区分前后,我们直接看全局:
式子:
模拟一下中间有箭头的那列,我们先枚举
上面红色的表示
当
当
这个过程仍然可以用线段树维护。
首先对于每个
对于一开始的式子:
把它分为
前者就是 用
3、被别的杯子倒回来水
竟然和第一步一样,只需要在第一遍从左向右扫过去的时候记一下此时的
到此为止 我们已经把一个
再仔细思考一下整个过程,其实就是带权矩形面积和。
闲话:
9G 听了 Qyun 的几句话之后说:“我会
码
/* 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'; }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 上周热点回顾(2.17-2.23)
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章