BZOJ.4826.[AHOI/HNOI2017]影魔(树状数组/莫队 单调栈)
之前看\(mjt\)用莫队写了,以为是一种正解,码了3h结果在LOJ T了没A= = 心态爆炸(upd:发现是用C++11(NOI)交的,用C++11交就快一倍了...)
深刻的体会到什么叫写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\)的前缀和,那么
维护\(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;
}
很久以前的奇怪但现在依旧成立的签名
attack is our red sun $$\color{red}{\boxed{\color{red}{attack\ is\ our\ red\ sun}}}$$ ------------------------------------------------------------------------------------------------------------------------