CF2009G. Yunli's Subarray Queries 题解
G1
题目要求,对于一个子区间 \(a_{l\sim l+k-1}\),最少要进行多少次单点修改,才能使 \(\forall \ l < i \leq l+k-1,a_i=a_{i-1}+1\),其中 \(k\) 是固定的。
对于这种问题,我们通常有一个 trick:将 \(a_i\) 变为 \(a_i-i\)。这样的话,我们要求的答案就变为了 \(k\) 减去变化后的 \(a_{l\sim l+k-1}\) 中众数的出现次数。这个可以用线段树或者莫队做。
以下是线段树的代码,为了避免出现 \(a_i < 0\),这里将 \(a_i\) 变为了 \(a_i-i+n\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int T,n,m,q,a[200005],ans[200005];
namespace SGT{
int t[400005<<2];
inline void pushup(int k){
t[k]=max(t[k<<1],t[k<<1|1]);return;
}
inline void build(int k=1,int l=1,int r=2*n){
if(l==r){
t[k]=0;
}else{
int mid=l+r>>1;
build(k<<1,l,mid),build(k<<1|1,mid+1,r);
pushup(k);
}return;
}
inline void updata(int P,int val,int k=1,int l=1,int r=2*n){
if(P<=l&&r<=P){
t[k]+=val;
}else{
int mid=l+r>>1;
if(P<=mid)updata(P,val,k<<1,l,mid);
else updata(P,val,k<<1|1,mid+1,r);
pushup(k);
}return;
}
}
inline int read(){
register int x(0),t(0);
static char ch=getchar();
while(!isdigit(ch)){t|=(ch=='-'),ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48),ch=getchar();}
return t?-x:x;
}
signed main(){
T=read();while(T--){
n=read(),m=read(),q=read();
for(int i=1;i<=n;i++){
a[i]=read()+n-i;
}
SGT::build();
for(int i=1;i<=m;i++){
SGT::updata(a[i],1);
}
ans[1]=m-SGT::t[1];
for(int i=2;i+m-1<=n;i++){
SGT::updata(a[i-1],-1);
SGT::updata(a[i+m-1],1);
ans[i]=m-SGT::t[1];
}
for(int i=1;i<=q;i++){
int l=read(),r=read();
cout<<ans[l]<<'\n';
}
}return 0;
}
时间复杂度为 \(O(n\times\log n)\)。
G2
我们先用 G1 的方法求出 \(f(\{a_i,a_{i+1},\dots, a_{i+k-1}\})\),并设其为 \(w_i\)。
与 G1 不同,G2 要求的的是 \(\sum \limits_{i=l}^{r-k+1} \min \limits_{j=l}^{i} w_j\),其中 \(r \geq l+k-1\)。
看到对一段前缀取最小值,我们考虑如何模拟出这一过程。设 \(p_0=l\),每次往后找到离 \(p_0\) 最近的满足 \(w_{p_0} > w_p\) 的 \(p\),此时 \(\min \limits_{j=l}^{i} w_j=w_{p_0}(p_0\le i\le p - 1)\),这段对答案的贡献为 \((p-p_0)\times w_{p_0}\) ,再将 \(p_0\) 设为 \(p\)。不断这样做下去,直到 \(p_0\) 后已经没有 \(w_{p_0}>w_p\) 或者 \(p_0>r-k+1\)。
我们不可能每次询问都这样暴力做一次,考虑如何优化。显然,对于每个 \(p_0\),其后最近的满足 \(w_{p_0} > w_p\) 的 \(p\) 都是不变的。考虑提前将每个 \(p_0\) 的 \(p\) 求出来,这个可以用单调栈求。然后我们需要用到另一个 trick:将 \(p\) 向 \(p_0\) 连一条长度为 \((p-p_0)\times w_{p_0}\) 的边,这样连出来一定是棵树。(我也不太清楚这个 trick 的名字,可能跟 AC 自动机的优化差不多。)
这样我们可以在树上二分出最后一个不大于 \(r-k+1\) 的 \(p_0\)。具体实现:先一遍 dfs 将 \(dep_i\) 和 \(dis_i\) 求出,其中 \(dp_i\) 表示 \(i\) 的深度,\(dis_i\) 表示 \(i\) 到根节点(为了方便设为 \(n-m+2\))的距离。接着将二分的左起始点设为 \(1\),右起始点设为 \(dep_l\),判断 \(l\) 在树上的 \(dep_l-mid\) 级祖先是否小于等于 \(r-k+1\),移动 \(l\) 和 \(r\) 进行二分,设二分出来的答案为 \(res\)。这时答案就为 \(dis_l-dis_{res}+(r-k-res+2)\times w_{res}\)。
代码如下:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int T,n,m,q,a[200005],w[200005];
vector <int> to[200005];
int st[200005],top;
namespace SGT{
int t[400005<<2];
inline void pushup(int k){
t[k]=max(t[k<<1],t[k<<1|1]);return;
}
inline void build(int k=1,int l=1,int r=2*n){
if(l==r){
t[k]=0;
}else{
int mid=l+r>>1;
build(k<<1,l,mid),build(k<<1|1,mid+1,r);
pushup(k);
}return;
}
inline void updata(int P,int val,int k=1,int l=1,int r=2*n){
if(P<=l&&r<=P){
t[k]+=val;
}else{
int mid=l+r>>1;
if(P<=mid)updata(P,val,k<<1,l,mid);
else updata(P,val,k<<1|1,mid+1,r);
pushup(k);
}return;
}
}
namespace SP{
#define Rana_Love true
int siz[200005],dfn[200005],dep[200005],fa[200005],top[200005],mxs[200005],fp[200005],dis[200005],cnt;
inline void dfs1(int x,int last){
dep[x]=dep[last]+1,fa[x]=last,siz[x]=1;
for(int y:to[x]){
if(y==last)continue;
dis[y]=dis[x]+(x-y)*w[y];
dfs1(y,x),siz[x]+=siz[y];
if(siz[y]>siz[mxs[x]])mxs[x]=y;
}return;
}
inline void dfs2(int x,int last){
dfn[x]=++cnt,fp[cnt]=x,top[x]=last;
if(!mxs[x])return;dfs2(mxs[x],last);
for(int y:to[x]){
if(y==fa[x]||y==mxs[x])continue;
dfs2(y,y);
}return;
}
inline void init(){
cnt=0;for(int i=1;i<=n-m+2;i++)mxs[i]=0;dfs1(n-m+2,0),dfs2(n-m+2,n-m+2);return;
}
inline int jump(int x,int deep){
int step=dep[x]-deep;
while(Rana_Love){
if(dep[x]-dep[top[x]]+1<=step){
step+=dep[top[x]]-dep[x]-1,x=fa[top[x]];
}else break;
}return fp[dfn[x]-step];
}
inline int solve(int st,int ed){
int l=1,r=dep[st];
while(l<r){
int mid=l+r>>1;
if(jump(st,mid)<=ed-m+1){
r=mid;
}else l=mid+1;
}r=jump(st,r);
return dis[st]-dis[r]+w[r]*(ed-m+1-r+1);
}
}
inline int read(){
register int x(0),t(0);
static char ch=getchar();
while(!isdigit(ch)){t|=(ch=='-'),ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48),ch=getchar();}
return t?-x:x;
}
signed main(){
T=read();while(T--){
n=read(),m=read(),q=read();
for(int i=1;i<=n;i++){
a[i]=read()+n-i;
}
for(int i=1;i<=n-m+2;i++){
to[i].clear();
}
SGT::build();
for(int i=1;i<=m;i++){
SGT::updata(a[i],1);
}w[1]=m-SGT::t[1];
for(int i=2;i+m-1<=n;i++){
SGT::updata(a[i-1],-1);
SGT::updata(a[i+m-1],1);
w[i]=m-SGT::t[1];
}
st[top=0]=n-m+2;
for(int i=n-m+1;i>=1;i--){
while(top&&w[st[top]]>=w[i])top--;
to[st[top]].push_back(i),st[++top]=i;
}
SP::init();
while(q--){
int l=read(),r=read();
cout<<SP::solve(l,r)<<'\n';
}
}return 0;
}
我的树上 k 级祖先是用重链剖分求的,因此时间复杂度为 \(O(n\times \log^2 n)\)。可以将重链剖分换成长链剖分之类的,时间复杂度可以降到 \(O(n\times\log n)\)。或许可以直接上历史和线段树?
G3
G3要求的东西变为了 \(\sum \limits_{i=l}^{r-k+1} \sum \limits_{j=i}^{r-k+1} \min \limits_{g=i}^{j} w_g\)。类似这种式子套上 P3246 [HNOI2016] 序列 的方法就行了。不知道能否将G2的方法继续扩展到G3,我还没想出来,如果可以的话还请告诉我!
这里仅仅贴一份用莫队实现的代码,具体思路参考 P3246 [HNOI2016] 序列 的题解:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int lim=200000,block=447;
int lg[200005];
int T,n,m,len,a[200005],w[200005],ans[200005];
namespace SGT{
int t[400005<<2];
inline void pushup(int k){
t[k]=max(t[k<<1],t[k<<1|1]);return;
}
inline void build(int k=1,int l=1,int r=2*n){
if(l==r){
t[k]=0;
}else{
int mid=l+r>>1;
build(k<<1,l,mid),build(k<<1|1,mid+1,r);
pushup(k);
}return;
}
inline void updata(int P,int val,int k=1,int l=1,int r=2*n){
if(P<=l&&r<=P){
t[k]+=val;
}else{
int mid=l+r>>1;
if(P<=mid)updata(P,val,k<<1,l,mid);
else updata(P,val,k<<1|1,mid+1,r);
pushup(k);
}return;
}
}
int dp[2][200005];
namespace ST{
int f[200005][25],pre[200005],nxt[200005],st[200005],top;
inline int comp(int A,int B){
return w[A]<w[B]?A:B;
}
inline void init(){
for(int i=1;i<=n-len+1;i++){
f[i][0]=i;
}
for(int j=1;1<<j<=n-len+1;j++){
for(int i=1;i+(1<<j)-1<=n-len+1;i++){
f[i][j]=comp(f[i][j-1],f[i+(1<<j-1)][j-1]);
}
}
dp[0][st[top=0]=0]=0;
for(int i=1;i<=n-len+1;i++){
while(top&&w[st[top]]>=w[i])top--;
pre[i]=st[top],st[++top]=i;
}
for(int i=1;i<=n;i++){
dp[0][i]=dp[0][pre[i]]+(i-pre[i])*w[i];
}
dp[1][st[top=0]=n-len+2]=0;
for(int i=n-len+1;i>=1;i--){
while(top&&w[st[top]]>=w[i])top--;
nxt[i]=st[top],st[++top]=i;
}
for(int i=n-len+1;i>=1;i--){
dp[1][i]=dp[1][nxt[i]]+(nxt[i]-i)*w[i];
}
return;
}
inline int query(int l,int r){
return comp(f[l][lg[r-l+1]],f[r-(1<<lg[r-l+1])+1][lg[r-l+1]]);
}
}
int lt,rt,res;
struct Rana{
int l,r,id;
}q[200005];
inline int idx(int x){return (x-1)/block+1;}
inline bool cmp(Rana A,Rana B){
return idx(A.l)!=idx(B.l)?idx(A.l)<idx(B.l):idx(A.r)<idx(B.r);
}
inline int left(int l,int r){
int pos=ST::query(l,r);
return w[pos]*(r-pos+1)+dp[1][l]-dp[1][pos];
}
inline int right(int l,int r){
int pos=ST::query(l,r);
return w[pos]*(pos-l+1)+dp[0][r]-dp[0][pos];
}
inline int read(){
register int x(0),t(0);
static char ch=getchar();
while(!isdigit(ch)){t|=(ch=='-'),ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48),ch=getchar();}
return t?-x:x;
}
signed main(){
for(int i=2;i<=lim;i++){
lg[i]=lg[i>>1]+1;
}T=read();while(T--){
n=read(),len=read(),m=read();
for(int i=1;i<=n;i++){
a[i]=read()-i+n;
}
SGT::build();
for(int i=1;i<=len;i++){
SGT::updata(a[i],1);
}w[1]=len-SGT::t[1];
for(int i=2;i+len-1<=n;i++){
SGT::updata(a[i-1],-1);
SGT::updata(a[i+len-1],1);
w[i]=len-SGT::t[1];
}
ST::init();
for(int i=1;i<=m;i++){
q[i]={read(),read()-len+1,i};
}
sort(q+1,q+1+m,cmp),lt=1,rt=0,res=0;
for(int i=1;i<=m;i++){
while(lt>q[i].l)res+=left(--lt,rt);
while(rt<q[i].r)res+=right(lt,++rt);
while(lt<q[i].l)res-=left(lt++,rt);
while(rt>q[i].r)res-=right(lt,rt--);
ans[q[i].id]=res;
}
for(int i=1;i<=m;i++){
cout<<ans[i]<<'\n';
}
}return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】