BZOJ.4826.[AHOI/HNOI2017]影魔(树状数组/莫队 单调栈)
之前看$mjt$用莫队写了,以为是一种正解,码了3h结果在LOJ T了没A= = 心态爆炸(upd:发现是用C11(NOI)交的,用C11交就快一倍了...)
深刻的体会到什么叫写bug...比着一个数据调,调对了询问$[1,5]\(又要调询问\)[2,7]\(,调过了\)[2,7]\(发现\)[1,5]$又不对...(如此循环*n次)
莫队 前缀和 单调栈:(非正解,不开O2 70分,开O2以及BZOJ算总时限可以A)
可以先做一下HNOI2016 序列,感觉算是那道题的加强版,因为要多维护好多东西。。
做完那题就容易发现我们需要维护什么了。假设我们要从$[l,r-1]\(转移到\)[l,r]$,首先找到区间最大值$p$的位置,然后$[l,p]\(之间的贡献很好算,维护两个前缀和表示前面/后面的单调子序列中,比它大的有多少个。麻烦的是\)[p+1,r]$里面的...
刚开始想的是再维护两个前缀和,查询的时候求一下$[L[r]+1,r-1]$($L[x]$为$x$左边第一个比$A_x$大的数)中的最大值,再同样统计一下。
然而细节贼特么多,写+调了3h,开O2过了,感动。
对比$mjt$的代码发现orz,莫队转移的时候多了一次查询最小值对效率影响特别大。。
又想了想发现一个前缀和就可以,然后又改+调~~+颓了2h。但是跑的比较快啦不开O2也有$70$分啦~~($mjt$$30$分2333)。
不是很好解释,但很好理解,只是细节问题。。
复杂度$O(n\sqrt n)$。
正解:
令$x$左边第一个比它大的点为$L$,右边第一个比它大的点为$R$。考虑三种情况:
- 对于区间$[L,R]$,答案为$P1$;
- 对于左端点在$L$,右端点在$x+1\sim R-1$的区间,答案各是$P2$;
- 对于左端点在$L+1\sim x-1$,右端点在$R$的区间,答案也都是$P2$。
(这里不算区间$[L,x]\(和\)[x,R]$的贡献,在子区间里算。。)
然后考虑离线,把询问按右端点排序。如果只有$1,3$两种情况,在$R$处,给每个对应范围内的左端点加上贡献即可。现在实际是求的一个左端点在$[q_l,q_r]$内的和,所以现在答案是更新到$R$时,区间$[q_l,q_r]$的和。
对于第$2$种情况比较麻烦,要拆成$O(n)$个单点加?
考虑把它变成区间加。注意到右端点在$p$处统计$L$的贡献$P2$,和在$L$处给右端点在$p$时的贡献加$P2$是一样的,所以就可以在$L$处给区间$x+1\sim R-1$加$P2$。
这样的话左端点的贡献可能会多算,更新到$L-1$处时,给$[q_l,q_r]\(的询问减去\)[q_l,q_r]$的和即可。
另一种理解方式:
也可以考虑把$[l,r]\(的贡献看做平面上的点\)(l,r)$。同样离线,如果只有$1,3$,在横坐标$R$处的对应区间加上对应的答案,询问$[l,r]\(就是横纵坐标都在\)[l,r]$的矩形的和。
对于$2$,同上面的分析,变成在$L$处给对应右端点加上贡献。虽然$(x,y)\(和\)(y,x)$的贡献相同,但这相当于把原先横着的长条变成竖着的。注意到每个询问都是对角线在$y=x$上的正方形,所以这么做是对的。
两种都是同样的区间加、区间求和,可以树状数组。
复杂度$O(n\log n)$。
另外对于$[i,i+1]$这种区间的答案没有统计到,加上即可。
再简单记一下树状数组区间修改、区间求和:
类似区间修改单点查询,维护差分数组$d_i$的前缀和,那么
\(\begin{aligned}sum[1..p]&=\sum_{i=1}^p\sum_{j=1}^id_j\\&=\sum_{i=1}^p(p-i+1)d_i\\&=(p+1)\sum_{i=1}^pd_i-\sum_{i=1}^pi\cdot d_i\end{aligned}\)
维护$d_i$和$i\cdot d_i$的前缀和即可(区间修改就是两个最一般的单点。不要想错...)。
莫队 前缀和 单调栈:
//26132kb 15528ms
#include <cmath>
#include <cstdio>
#include <cctype>
#include <algorithm>
//#define gc() getchar()
#define MAXIN 300000
#define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
typedef long long LL;
const int N=2e5+5,INF=1<<30;
int P1,P2,bel[N],A[N],ref[N],st[18][N],Log[N],sk[N],L[N],R[N],sl[N],sr[N];
char IN[MAXIN],*SS=IN,*TT=IN;
struct Quries
{
int l,r,id;
bool operator <(const Quries &x)const
{
return bel[l]==bel[x.l]?r<x.r:bel[l]<bel[x.l];
}
}q[N];
inline int read()
{
int now=0;register char c=gc();
for(;!isdigit(c);c=gc());
for(;isdigit(c);now=now*10+c-48,c=gc());
return now;
}
inline int Query(int l,int r)//return pos
{
int k=Log[r-l+1];
return ref[std::max(st[k][l],st[k][r-(1<<k)+1])];//比写A[x]>A[y]?x:y 快0.4s+...
}
void Init_ST(const int n)
{
for(int i=2; i<=n; ++i) Log[i]=Log[i>>1]+1;
for(int j=1; j<=Log[n]; ++j)
for(int t=1<<j-1,i=n-t; i; --i)
st[j][i]=std::max(st[j-1][i],st[j-1][i+t]);
}
inline LL UpdL(int l,int r)
{
if(l==r) return 0;
int p=Query(l+1,r),R=std::min(p,::R[l]),tot=R-l,num=sl[l+1]-sl[R]+1;
return (LL)num*P1+(tot-num)*P2+(R<p?(sl[R]-sl[p])*P2:(::R[l]>r)?(r-p)*P2:0);
}
inline LL UpdR(int l,int r)
{
if(l==r) return 0;
int p=Query(l,r-1),L=std::max(p,::L[r]),tot=r-L,num=sr[r-1]-sr[L]+1;
return (LL)num*P1+(tot-num)*P2+(L>p?(sr[L]-sr[p])*P2:(::L[r]<l)?(p-l)*P2:0);
}
int main()
{
static LL Ans[N];
// freopen("sf.in","r",stdin);
// freopen("sf.out","w",stdout);
const int n=read(),Q=read(),P1=read(),P2=read(),size=sqrt(n); ::P1=P1,::P2=P2;
for(int i=1; i<=n; ++i) st[0][i]=A[i]=read(), ref[A[i]]=i, bel[i]=i/size;
for(int i=1; i<=Q; ++i) q[i]=(Quries){read(),read(),i};
std::sort(q+1,q+1+Q);
Init_ST(n);
A[sk[0]=0]=INF;
for(int i=1,top=0; i<=n; ++i)//sr[i]:递减子序列中,i左边有多少个比它大的数 sr2[i]:贡献2
{
while(A[sk[top]]<=A[i]) --top;//R[sk[top--]]=i;
sr[i]=sr[L[i]=sk[top]]+1, sk[++top]=i;//sr2[i]=sr2[sk[top]]+(i-sk[top]-1)*P2+P1,
}
A[sk[0]=n+1]=INF;
for(int i=n,top=0; i; --i)
{
while(A[sk[top]]<=A[i]) --top;//L[sk[top--]]=i;
sl[i]=sl[R[i]=sk[top]]+1, sk[++top]=i;//sl2[i]=sl2[sk[top]]+(sk[top]-i-1)*P2+P1,
}
LL Now=0;
for(int l=1,r=0,i=1; i<=Q; ++i)
{
int ln=q[i].l,rn=q[i].r;
while(l>ln) Now+=UpdL(--l,r);
while(r<rn) Now+=UpdR(l,++r);
while(l<ln) Now-=UpdL(l++,r);
while(r>rn) Now-=UpdR(l,r--);
Ans[q[i].id]=Now;
}
for(int i=1; i<=Q; printf("%lld\n",Ans[i++]));
return 0;
}
树状数组:
//25340kb 2440ms
#include <cmath>
#include <cstdio>
#include <cctype>
#include <algorithm>
//#define gc() getchar()
#define MAXIN 300000
#define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
typedef long long LL;
const int N=2e5+5,INF=1<<30;
int A[N],sk[N],L[N],R[N];
char IN[MAXIN],*SS=IN,*TT=IN;
struct Quries
{
int l,r,p,id,val;
bool operator <(const Quries &x)const
{
return p<x.p;
}
}q[N<<1];
struct Opt
{
int l,r,p,val;
bool operator <(const Opt &x)const
{
return p<x.p;
}
}opt[N*3];
struct BIT
{
int n,t1[N]; LL t2[N];
#define lb(x) (x&-x)
inline void Add(int p,int v)
{
for(int v2=p*v; p<=n; p+=lb(p)) t1[p]+=v,t2[p]+=v2;
}
inline void Modify(int l,int r,int v)
{
Add(l,v), Add(r+1,-v);
}
inline LL Query2(int x)
{
LL res1=0,res2=0;
for(int p=x; p; p^=lb(p)) res1+=t1[p],res2+=t2[p];
return res1*(x+1)-res2;
}
inline LL Query(int l,int r)
{
return Query2(r)-Query2(l-1);
}
}T;
inline int read()
{
int now=0;register char c=gc();
for(;!isdigit(c);c=gc());
for(;isdigit(c);now=now*10+c-48,c=gc());
return now;
}
int main()
{
static LL Ans[N];
// freopen("sf.in","r",stdin);
// freopen("sf.out","w",stdout);
const int n=read(),Q=read(),P1=read(),P2=read(),Q2=Q<<1;
for(int i=1; i<=n; ++i) A[i]=read();
for(int i=1,l,r,t=0; i<=Q; ++i)
l=read(), r=read(), Ans[i]+=(r-l)*P1, q[++t]=(Quries){l,r,l-1,i,-1}, q[++t]=(Quries){l,r,r,i,1};
std::sort(q+1,q+1+Q2);
int top=0; A[sk[0]=0]=INF;
for(int i=1; i<=n; ++i)
{
while(A[sk[top]]<=A[i]) R[sk[top--]]=i;
L[i]=sk[top], sk[++top]=i;
}
while(top) R[sk[top--]]=n+1;
int tot=0;
for(int i=1; i<=n; ++i)
{
int l=L[i],r=R[i];
if(l&&r<=n) opt[++tot]=(Opt){l,l,r,P1};
if(l&&i+1<r) opt[++tot]=(Opt){i+1,r-1,l,P2};//有左端点才会有这个贡献
if(l+1<i&&r<=n) opt[++tot]=(Opt){l+1,i-1,r,P2};
}
std::sort(opt+1,opt+1+tot);
T.n=n, opt[tot+1].p=N, q[Q2+1].p=N;
int nq=1,no=1;
while(!q[nq].p) ++nq;//!
for(int i=1; i<=n&&nq<=Q2; ++i)
{
while(opt[no].p==i) T.Modify(opt[no].l,opt[no].r,opt[no].val), ++no;
while(q[nq].p==i) Ans[q[nq].id]+=q[nq].val*T.Query(q[nq].l,q[nq].r), ++nq;
}
for(int i=1; i<=Q; printf("%lld\n",Ans[i++]));
return 0;
}
放一份第一次写的莫队:
#include <cmath>
#include <cstdio>
#include <cctype>
#include <algorithm>
#define MAXIN 300000
#define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
typedef long long LL;
const int N=2e5+5,INF=1<<30;
int P1,P2,bel[N],A[N],ref[N],st[18][N],Log[N],sk[N],L[N],R[N],sl[N],sr[N];
LL sl2[N],sr2[N];
char IN[MAXIN],*SS=IN,*TT=IN;
struct Quries
{
int l,r,id;
bool operator <(const Quries &x)const
{
return bel[l]==bel[x.l]?r<x.r:bel[l]<bel[x.l];
}
}q[N];
inline int read()
{
int now=0;register char c=gc();
for(;!isdigit(c);c=gc());
for(;isdigit(c);now=now*10+c-48,c=gc());
return now;
}
inline int Query(int l,int r)//return pos
{
int k=Log[r-l+1];
return ref[std::max(st[k][l],st[k][r-(1<<k)+1])];
}
void Init_ST(const int n)
{
for(int i=2; i<=n; ++i) Log[i]=Log[i>>1]+1;
for(int j=1; j<=Log[n]; ++j)
for(int t=1<<j-1,i=n-t; i; --i)
st[j][i]=std::max(st[j-1][i],st[j-1][i+t]);
}
inline LL UpdL(int l,int r)
{
if(l==r) return 0;
int p=Query(l,r),R=std::min(r+1,::R[l]);
LL delta=0;
if(l+1<R)
{
int p2=Query(l+1,R-1);
delta=sl2[l+1]-sl2[p2]+(R-p2-1)*P2+P1;
}
return delta+(R>r?0:R==r?P1:P1+(sl[l]-sl[p]-1)*P2);
}
inline LL UpdR(int l,int r)
{
if(l==r) return 0;
int p=Query(l,r),L=std::max(l-1,::L[r]);
LL delta=0;
if(L+1<r)
{
int p2=Query(L+1,r-1);
delta=sr2[r-1]-sr2[p2]+(p2-L-1)*P2+P1;
}
return delta+(L<l?0:L==l?P1:P1+(sr[r]-sr[p]-1)*P2);
}
int main()
{
static LL Ans[N];
// freopen("sf.in","r",stdin);
// freopen("sf.out","w",stdout);
const int n=read(),Q=read(),P1=read(),P2=read(),size=sqrt(n); ::P1=P1,::P2=P2;
for(int i=1; i<=n; ++i) st[0][i]=A[i]=read(), ref[A[i]]=i, bel[i]=i/size;
for(int i=1; i<=Q; ++i) q[i]=(Quries){read(),read(),i};
std::sort(q+1,q+1+Q);
Init_ST(n);
int top=0; A[sk[0]=0]=INF;
for(int i=1,v; i<=n; ++i)//sr[i]:递减子序列中,i左边有多少个比它大的数
{
while(A[sk[top]]<=A[i]) R[sk[top--]]=i;
sr[i]=sr[v=sk[top]]+1, sr2[i]=sr2[v]+(i-v-1)*P2+P1, sk[++top]=i;
}
while(top) R[sk[top--]]=n+1;
top=0, A[sk[0]=n+1]=INF;
for(int i=n,v; i; --i)
{
while(A[sk[top]]<=A[i]) L[sk[top--]]=i;
sl[i]=sl[v=sk[top]]+1, sl2[i]=sl2[v]+(v-i-1)*P2+P1, sk[++top]=i;
}
while(top) L[sk[top--]]=0;
LL Now=0;
for(int l=1,r=0,i=1; i<=Q; ++i)
{
int ln=q[i].l,rn=q[i].r;
while(l>ln) Now+=UpdL(--l,r);
while(r<rn) Now+=UpdR(l,++r);
while(l<ln) Now-=UpdL(l++,r);
while(r>rn) Now-=UpdR(l,r--);
Ans[q[i].id]=Now;
}
for(int i=1; i<=Q; printf("%lld\n",Ans[i++]));
return 0;
}
别来无恙 你在心上
------------------------------------------------------------------------------------------------------------------------

浙公网安备 33010602011771号