把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷4501】[ZJOI2018] 胖(二分+RMQ)

点此看题面

  • 有一条长度为\(n\)的链(点编号为\(1\sim n\)),链上每条边有一个边权。
  • \(q\)次询问,每次给出\(k_i\)条从\(0\)号点连出的对应节点各不相同的边,问这张图上跑\(Bellman-Ford\)算法的复杂度。
  • \(n,q,\sum k_i\le2\times10^5\)

首先考虑二分答案

我们单独考虑\(0\)号点连出的每一条边,那么对应点能够更新到的点必然是一段连续的区间。

所以容易想到二分答案。

如果想直接线段树上二分,就会发现这有些困难,因为有些离当前点距离近但是点数比较多的点是无法造成影响的。

因此我们只能考虑二分答案之后再验证,尽管这样一来复杂度是两个\(log\),但还是能过的。

\(RMQ\)

说起来好久没写过\(RMQ\)了。。。

其实我首先想到的是线段树,然而听说可能会\(T\)掉(讲道理由于仍然需要使用\(lower\_bound\)\(upper\_bound\),复杂度应该没有区别),因此就去写了个\(RMQ\)

其实就是考虑对于当前二分到的点\(mid\)来说,第\(i\)条边到\(mid\)的距离无非两种情况:

  • \(p_i\le mid\)\(l_i+d_{p_i}-d_{mid}\)
  • \(p_i>mid\)\(l_i-d_{p_i}+d_{mid}\)

所以我们只要开两个\(RMQ\),分别维护出\(l_i+d_{p_i}\)\(l_i-d_{p_i}\)的最小值即可。

注意两条边同时到达某一个点的情况要特殊讨论,在距离相同的时候注意不要重复计算。

代码:\(O(nlog^2n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200000
#define LN 20
#define LL long long
#define LB(x) (lower_bound(s+1,s+k+1,Data(x))-s)
#define UB(x) (upper_bound(s+1,s+k+1,Data(x))-s)
using namespace std;
int n,k,LG[N+5];LL d[N+5];
struct Data
{
	int p,v;I Data(CI x=0,CI y=0):p(x),v(y){}
	I bool operator < (Con Data& o) Con {return p<o.p;}
}s[N+5];
class FastIO
{
	private:
		#define FS 100000
		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
		#define pc(c) (C==E&&(clear(),0),*C++=c)
		#define D isdigit(c=tc())
		int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
	public:
		I FastIO() {A=B=FI,C=FO,E=FO+FS;}
		Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
		Tp I void writeln(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);pc('\n');}
		I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
}F;
struct RMQ
{
	LL V[N+5][LN+5];I void Init()//初始化
	{
		for(RI j=1,i;(1<<j)<=k;++j) for(i=1;i+(1<<j)-1<=k;++i) V[i][j]=min(V[i][j-1],V[i+(1<<j-1)][j-1]);
	}
	I LL Q(CI l,CI r) {if(l>r) return 1e18;RI t=LG[r-l+1];return min(V[l][t],V[r-(1<<t)+1][t]);}//询问区间最值
}A,B;
int main()
{
	RI Qt,i,j;for(F.read(n),F.read(Qt),LG[0]=-1,i=1;i<=n;++i) LG[i]=LG[i>>1]+1;//预处理log2
	RI l,r,x,mid;LL t;for(i=2;i<=n;++i) F.read(d[i]),d[i]+=d[i-1];W(Qt--)
	{
		for(F.read(k),i=1;i<=k;++i) F.read(s[i].p),F.read(s[i].v);sort(s+1,s+k+1),s[k+1].p=0;//一个坑点,因为我用了lower_bound,故要把s[k+1]清零
		for(i=1;i<=k;++i) A.V[i][0]=s[i].v+d[s[i].p],B.V[i][0]=s[i].v-d[s[i].p];//初始化RMQ
		for(A.Init(),B.Init(),t=0,i=1;i<=k;++i)//枚举每条边计算贡献
		{
			#define D(i,w) (abs(d[s[i].p]-d[w])+s[i].v)//计算第i条边到点w的距离
			l=1,r=x=s[i].p;W(mid=l+r-1>>1,l^r)//二分左边界
			{
				if(s[j=LB(2*mid-x)].p==2*mid-x&&D(j,mid)<=D(i,mid)) {l=mid+1;continue;}//特判两点同时到达
				min(A.Q(LB(mid),i-1)-d[mid],B.Q(UB(2*mid-x),LB(mid)-1)+d[mid])>D(i,mid)?r=mid:l=mid+1;//判断
			}t+=x-r+1;//统计左半部分答案
			l=s[i].p,r=n;W(mid=l+r+1>>1,l^r)
			{
				if(s[j=LB(2*mid-x)].p==2*mid-x&&D(j,mid)<D(i,mid)) {r=mid-1;continue;}//特判两点同时到达,这里不能再写=
				min(A.Q(LB(mid),LB(2*mid-x)-1)-d[mid],B.Q(i+1,LB(mid)-1)+d[mid])>D(i,mid)?l=mid:r=mid-1;//判断
			}t+=l-x;//统计右半部分答案
		}F.writeln(t);
	}return F.clear(),0;
}
posted @ 2021-01-20 20:46  TheLostWeak  阅读(70)  评论(0编辑  收藏  举报