首页 > 技术文章 > Loj #2529. 「ZJOI2018」胖

hchhch233 2019-05-02 18:59 原文

Loj #2529. 「ZJOI2018」胖

题目描述

Cedyks 是九条可怜的好朋友(可能这场比赛公开以后就不是了),也是这题的主人公。

Cedyks 是一个富有的男孩子。他住在著名的 The Place(宫殿)中。

Cedyks 是一个努力的男孩子。他每天都做着不一样的题来锻炼他的 The Salt (灵魂)。这天,他打算在他的宫殿外围修筑一道城墙,城墙上有 \(n\) 座瞭望塔。你可以把城墙看做一条线段,瞭望塔是线段上的 \(n\) 个点,其中 \(1\)\(n\) 分别为城墙的两个端点。其中第 \(i\) 座瞭望塔和第 \(i + 1\) 座瞭望塔的距离为 \(w_i\) ,他们之间的道路是双向的。

城墙很快就修建好了,现在 Cedyks 开始计划修筑他的宫殿到城墙的道路。因为这题的题目名称,Cedyks 打算用他的宫殿到每一个瞭望塔的最短道路之和来衡量一个修建计划。

现在 Cedyks 手上有 \(m\) 个设计方案,第 \(k\) 个设计方案会在宫殿和瞭望塔之间修建 \(T_k\) 条双向道路,第 \(i\) 条道路连接着瞭望塔 \(a_i\) ,长度为 \(l_i\)

计算到每一个瞭望塔的最短路之和是一个繁重的工程,本来 Cedyks 想用广为流传的 SPFA 算法来求解,但是因为他的 butter (缓冲区)实在是太小了,他只能转而用原始的贝尔福特曼算法来计算,算法的流程大概如下:

\1. 定义宫殿是 \(0\) 号点,第 \(i\) 个瞭望塔是 \(i\) 号点,双向边 \((u_i , v_ i , l_i)\) 为一条连接 \(u_i\)\(v_i\) 的双向道路。令 \(d\) 为距离数组,最开始 \(d_0 = 0 , d_i = 10^{18} (i \in [1 , n])\)

\2. 令辅助数组 \(c = d\) 。依次对于每一条边 \((u_i , v_i , w_i)\) 进行增广,\(c_{u_i} = \min(c_{u_i} , d_{v_i} + w_i)\)\(c_{v_i} = \min(c_{v_i} , d_{u_i} + w_i)\)

\3. 令 \(t\)\(c\)\(d\) 中不一样的位置个数,即令 $S = {i | c_i \ne d_i } $ ,则 \(t = |S|\) 。若 \(t = 0\) ,说明 \(d\) 就是最终的最短路,算法结束。否则令 \(d = c\) ,回到第二步。

因为需要计算的设计方案实在是太多了,所以 Cedyks 雇佣了一些人来帮他进行计算。为了避免这些人用捏造出来的数据偷懒,他定义一个设计方案的校验值为在这个方案上运行贝尔福特曼算法每一次进入第三步 \(t\) 的和。他会让好几个雇佣来的人计算同样的设计方案,并比对每一个人给出的校验值。

你是 Cedyks 雇佣来的苦力之一,聪明的你发现在这个情形下计算最短路的长度的和是一件非常简单的事情。但是寄人篱下不得不低头,你不得不再计算出每一个方案的校验值来交差。

输入格式

第一行输入两个整数 \(n,m\) ,表示瞭望塔个数和设计方案个数。

接下来一行 \(n-1\) 个数 \(w_i\) ,表示瞭望塔 \(i\)\(i + 1\) 之间道路的长度。

接下来 \(m\) 行,每行描述一个设计方案。第一个整数 \(K\) 表示设计方案中的道路数量,接下来

\(K\) 个数对 \((a_i , l_i)\) 为一条宫殿到瞭望塔的边。

输出格式

对于每一个设计方案,输出一行一个整数表示校验值。

数据范围与提示

对于 \(100\%\) 的数据,保证每个设计方案 \(a_i\) 两两不同且 \(1 \le a_i \le n\)

对于 \(100\%\) 的数据,保证 \(1 \le w_i , l_i \le 10^9 , 1 \le \sum K \le 2 \times 10^5\)

\(\\\)

我们考虑计算每个\(a_i\)可以更新的点。每个\(a_i\)可以更新的点一定是一段连续的区间,所以我们就可以二分左端点和右端点。设\(pre_i\)\(1\)号点到\(i\)号点的距离。

以左端点为例,假设二分的是\(x\),那么容易发现\([2*x-a_i,x]\)的节点会比\(a_i\)先更新\(x\),假设存在\(a_j\in[2*x-a_i,x]\)。设\(pre_i\)\(1\)号点到\(i\)号点的距离,那么\(a_j\)就可能将\(x\)更新为\(pre_x-pre_{a_j}+l_j\),我们需要判断一下\(\min\{pre_x-pre_{a_j}+l_j\}\)\(pre_{a_i}-pre_x+l_i\)的关系就可以知道是否合法了。找最小值可以先二分出端点然后用\(ST\)表,常数小一些。

还要注意\((x,a_i)\)中的点可能比\(a_i\)更优。假设这个点为\(a_j\),也就是\(pre_{a_j}-pre_x+l_j\leq pre_{a_i}-pre_x+l_i\Rightarrow pre_{a_j}+l_j\leq pre_{a_i}+l_i\),这个拿个单调队列就可以判了。

代码:

#include<bits/stdc++.h>
#define ll long long
#define N 200005

using namespace std;
inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;}

int n,m,k;
ll w[N],pre[N];

struct road {
	int a;
	ll d;
	bool operator <(const road &x)const {return a<x.a;}
}s[N];

int lg[N];
ll mnp[N][20],mns[N][20];
int L[N],R[N];

int Upp(int p) {
	if(s[k].a<p) return k+1;
	int l=1,r=k,mid;
	while(l<r) {
		mid=l+r>>1;
		if(s[mid].a>=p) r=mid;
		else l=mid+1;
	}
	return l;
}

int Low(int p) {
	if(s[1].a>p) return -1;
	int l=1,r=k,mid;
	while(l<r) {
		mid=l+r+1>>1;
		if(s[mid].a<=p) l=mid;
		else r=mid-1;
	}
	return l;
}

ll query(ll ST[N][20],int l,int r) {
	int k=lg[r-l+1];
	return min(ST[l][k],ST[r-(1<<k)+1][k]);
}

bool chkl(int v,int p,ll d) {
	if(v==p) return 1;
	int L=Upp(2*p-v),R=Low(p);
	if(L>R) return 1;
	else return pre[p]+query(mnp,L,R)>pre[v]-pre[p]+d;
}

void cal_l() {
	static int st[N],top;
	static int l,r,mid;
	s[0].a=0;
	st[top=0]=0;
	for(int i=1;i<=k;i++) {
		while(top>0&&s[st[top]].d+pre[s[st[top]].a]>s[i].d+pre[s[i].a]) top--;
		l=s[st[top]].a+1,r=s[i].a;
		while(l<r) {
			mid=l+r>>1;
			if(chkl(s[i].a,mid,s[i].d)) r=mid;
			else l=mid+1;
		}
		L[i]=l;
		st[++top]=i;
	}
}

bool chkr(int v,int p,ll d) {
	if(v==p) return 1;
	int L=Upp(p),R=Low(2*p-v-1);
	if(L<=R&&query(mns,L,R)-pre[p]<=pre[p]-pre[v]+d) return 0;
	int x=Low(2*p-v);
	if(x==-1||s[x].a!=2*p-v) return 1;
	return pre[p]-pre[v]+d<=pre[s[x].a]-pre[p]+s[x].d;
}

void cal_r() {
	static int st[N],top;
	static int l,r,mid;
	st[top=0]=k+1;
	s[k+1].a=n+1;
	for(int i=k;i>=1;i--) {
		while(top>0&&s[st[top]].d-pre[s[st[top]].a]>s[i].d-pre[s[i].a]) top--;
		l=s[i].a,r=s[st[top]].a-1;
		while(l<r) {
			mid=l+r+1>>1;
			if(chkr(s[i].a,mid,s[i].d)) l=mid;
			else r=mid-1;
		}
		R[i]=l;
		st[++top]=i;
	}
}

int main() {
	lg[1]=0;
	for(int i=2;i<=200000;i++) lg[i]=lg[i>>1]+1;
	n=Get(),m=Get();
	for(int i=1;i<n;i++) w[i]=Get();
	for(int i=2;i<=n;i++) pre[i]=pre[i-1]+w[i-1];
	while(m--) {
		k=Get();
		for(int i=1;i<=k;i++) s[i].a=Get(),s[i].d=Get();
		sort(s+1,s+1+k);
		for(int i=1;i<=k;i++) {
			mnp[i][0]=s[i].d-pre[s[i].a];
			mns[i][0]=s[i].d+pre[s[i].a];
		}
		for(int j=1;j<=lg[k];j++) {
			for(int i=1;i<=k;i++) {
				if(i+(1<<j)-1<=k) {
					mns[i][j]=min(mns[i][j-1],mns[i+(1<<j-1)][j-1]);
					mnp[i][j]=min(mnp[i][j-1],mnp[i+(1<<j-1)][j-1]);
				}
			}
		}
		cal_l(),cal_r();
		ll ans=0;
		for(int i=1;i<=k;i++) {
			ans+=R[i]-L[i]+1;
		}
		cout<<ans<<"\n";
	}
	return 0;
}

推荐阅读