博客园 首页 私信博主 显示目录 隐藏目录 管理 动画

BZOJ.4826.[AHOI/HNOI2017]影魔(树状数组/莫队 单调栈)

BZOJ
LOJ
洛谷

之前看\(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\)。考虑三种情况:

  1. 对于区间\([L,R]\),答案为\(P1\)
  2. 对于左端点在\(L\),右端点在\(x+1\sim R-1\)的区间,答案各是\(P2\)
  3. 对于左端点在\(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;
}
posted @ 2019-02-25 22:37  SovietPower  阅读(153)  评论(0编辑  收藏  举报