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

点此看题面

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

首先考虑二分答案

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

所以容易想到二分答案。

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

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

(RMQ)

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

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

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

  • (p_ile 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('
');}
		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;
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu4501.html