1. 程式人生 > >bzoj5308[Zjoi2018]胖(線段樹,二分,st表)

bzoj5308[Zjoi2018]胖(線段樹,二分,st表)

Description
Cedyks是九條可憐的好朋友(可能這場比賽公開以後就不是了),也是這題的主人公。
Cedyks是一個富有的男孩子。他住在著名的ThePLace(宮殿)中。
Cedyks是一個努力的男孩子。他每天都做著不一樣的題來鍛鍊他的The SaLt(靈魂)。
這天,他打算在他的宮殿外圍修築一道城牆,城牆上有n座瞭望塔。
你可以把城牆看做一條線段,瞭望塔是線段上的n個點,其中1和n分別為城牆的兩個端點。
其中第i座瞭望塔和第i+1座瞭望塔的距離為wi,他們之間的道路是雙向的。
城牆很快就修建好了,現在Cedyks開始計劃修築他的宮殿到城牆的道路。
因為這題的題目名稱,
Cedyks打算用他的宮殿到每一個瞭望塔的最短道路之和來衡量一個修建計劃。
現在Cedyks手上有m個設計方案,第k個設計方案會在宮殿和瞭望塔之間修建Tk條雙向道路,
第i條道路連線著瞭望塔ai,長度為Li。
計算到每一個瞭望塔的最短路之和是一個繁重的工程,本來Cedyks想用廣為流傳的SPFA演算法
來求解,但是因為他的butter(緩衝區)實在是太小了,他只能轉而用原始的貝爾福特曼演算法
來計算,演算法的流程大概如下:
1:定義宮殿是0號點,第i個瞭望塔是i號點,雙向邊(ui,vi,Li)為一條連線ui和vi的雙向道路。
令d為距離陣列,最開始d0=0,di=10^18(i∈[1,n])。
2:令輔助陣列c=d。依次對於每一條邊(ui,vi,wi)進行增廣,
cui=min(cui,dvi+wi),
cvi=min(cvi,dui+wi)。
3:令t為c和d中不一樣的位置個數,即令S={i|ci!=di},則t=S。若t=0,說明d
就是最終的最短路,演算法結束。否則令d=c,回到第二步。
因為需要計算的設計方案實在是太多了,所以Cedyks僱傭了一些人來幫他進行計算。
為了避免這些人用捏造出來的資料偷懶,他定義一個設計方案的校驗值為在這個方案
上執行貝爾福特曼演算法每一次進入第三步t的和。他會讓好幾個僱傭來的人計算同樣
的設計方案,並比對每一個人給出的校驗值。
你是Cedyks僱傭來的苦力之一,聰明的你發現在這個情形下計算最短路的長度的和
是一件非常簡單的事情。但是寄人籬下不得不低頭,你不得不再計算出每一個方案
的校驗值來交差。
Input
第一行輸入兩個整數n,m,表示瞭望塔個數和設計方案個數。
接下來一行n-1個數wi,表示瞭望塔i和i+1之間道路的長度。
接下來m行,每行描述一個設計方案。
第一個整數K表示設計方案中的道路數量,
接下來K個數對(ai,Li)為一條宮殿到瞭望塔的邊。
1 <= wi, li <= 109, 1 <= ∑ K <= 2 × 10^5
N,M<=2*10^5
Output
對於每一個設計方案,輸出一行一個整數表示校驗值。
Sample Input
5 5
2 3 1 4
1 2 2
2 1 1 4 10
3 1 1 3 1 5 1
3 1 10 2 100 5 1
5 1 1 2 1 3 1 4 1 5 1
Sample Output
5
8
5
8
5


s o l u t i o n solution


省選兩天裡面最水的一道題
考慮到一個關鍵點能有貢獻的答案必然是一段連續的區間
就可以考慮二分了。

下面討論一下二分右端點的過程,左端點類似
我們先把其他的點查分到左右端點的值計算一下
對於一個 m i d mid ,我只要考慮 [

p o s , m i d ] [pos,mid] ( p o s pos 為關鍵點的位置)區間內的關鍵點查分到左邊的值以及 [ m i d , n ] [mid,n] 內的關鍵點查分到右邊的值

還要考慮相等的時候
細節比較多

剛剛那個過程顯然可以線段樹做。(普通線段樹可能被卡常,要zkw線段樹)
複雜度 O ( n l o g 2 n ) O(nlog^2n)

實際上我們發現我們在做的時候時不用修改的,我們用 s t st 表就行了,一開始 s b sb 了,以為建立 s t st 表的總複雜度是 m n l o g n mnlogn 的,後來意識到只要對關鍵點建立就行了

複雜度 O ( n l o g n ) O(nlogn)
s t st 表程式碼沒寫,和線段樹差別不大

線段樹程式碼:

#include<bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i = j;i <= k;++i)
#define repp(i,j,k) for(int i = j;i >= k;--i)
#define rept(i,x) for(int i = linkk[x];i;i = e[i].n)
#define Pll pair<ll,ll>
#define pb push_back 
#define pc putchar
#define mp make_pair
#define file(k) memset(k,0,sizeof(k))
#define ll long long
#define ls rt<<1
#define rs rt<<1|1
#define fr first
#define se second
int rd()
{
    int num = 0;char c = getchar();bool flag = true;
    while(c < '0'||c > '9') {if(c == '-') flag = false;c = getchar();}
    while(c >= '0' && c <= '9') num = num*10+c-48,c = getchar();
    if(flag) return num;else return -num;
}
const ll INF = 1e17;
inline ll min(ll a,ll b){return a<b?a:b;}
int n,m,k,flen;
int v[201000];
struct point{int pl,v;}a[201000];
ll sum[201000],_sum[201000];
//sum表示字首和,_sum表示字尾和
//0表示從前開始的查分,1表示從後
namespace tree
{
	ll mn[2][801000];
	void change(int x,ll a,ll b)
	{
		x += flen;mn[0][x] = a;mn[1][x] = b;
		while(x)
		    x>>=1,
		    mn[0][x] = min(mn[0][x<<1],mn[0][x<<1|1]),
		    mn[1][x]     = min(mn[1][x<<1],mn[1][x<<1|1]);
	}
	ll query(int l,int r,int op)
	{
		if(l<1) l = 1;
		if(r>n) r = n;
		if(l>r) return INF;
		ll ans = INF;
		for(l+=flen-1,r+=flen+1;l^r^1;l>>=1,r>>=1)
		{
			if(~l&1) ans = min(ans,mn[op][l^1]);
			if(r&1)  ans = min(ans,mn[op][r^1]);
		}
		return ans;
	}
}using namespace tree;
bool mycmp(point x,point y){return x.pl < y.pl;}
void work()
{
	rep(i,1,k) tree::change(a[i].pl,1ll*a[i].v-sum[a[i].pl],1ll*a[i].v-_sum[a[i].pl]);
	ll ans = 0;
	rep(i,1,k) 
	{
	    //考慮第i個關鍵點向右的貢獻,相同算貢獻 
		int l = a[i].pl,r = n+1;
		while(l+1<r)
		{
			int mid = l+r>>1;
			ll len = sum[mid] - sum[a[i].pl] + a[i].v,tp;//距離
			//先處理端點 
			tp = tree::query(2*mid-a[i].pl,2*mid-a[i].pl,1);
			if(tp + _sum[mid] < len){r = mid;continue;}
			//一般情況 
			tp = (a[i].pl+1<=mid)?tree::query(a[i].pl+1,mid,0):INF;
			if(tp + sum[mid] <= len) {r = mid;continue;}
			len -= _sum[mid];//查分到右端 
			tp = (mid<=2*mid-a[i].pl-1)?tree::query(mid,2*mid-a[i].pl-1,1):INF;
			if(len >= tp) {r = mid;continue;}
			l = mid;
		}
		ans += l-a[i].pl+1;
		//考慮向左的貢獻,相同不算貢獻
		l = 0,r = a[i].pl;
		while(l+1<r)
		{
			int mid = l+r>>1;
			ll len = sum[a[i].pl] - sum[mid] + a[i].v,tp;
			//先處理端點
			tp = tree::query(2*mid-a[i].pl,2*mid-a[i].pl,0);
			if(tp + sum[mid] <= len){l = mid;continue;}
			//一般情況 
			tp = (mid<=a[i].pl-1)?tree::query(mid,a[i].pl-1,1):INF;
			if(tp + _sum[mid] <= len) {l = mid;continue;}
			len -= sum[mid];//查分到左側
			tp = (2*mid-a[i].pl+1<=mid)?tree::query(2*mid-a[i].pl+1,mid,0):INF;
			if(len >= tp) {l = mid;continue;}
			r = mid;
		}
		ans += a[i].pl - r;
	}
	rep(i,1,k) change(a[i].pl,INF,INF);
	printf("%lld\n",ans);
}
int main()
{
	n = rd();m = rd();flen = 1;while(flen < n) flen <<= 1;
	rep(i,1,4*n) mn[0][i] = mn[1][i] = INF;
	rep(i,1,n-1) v[i] = rd(),sum[i+1] = sum[i] + v[i];
	repp(i,n-1,1) _sum[i] = _sum[i+1] + v[i];
	rep(i,1,m)
	{
		k = rd();
		rep(j,1,k) a[j].pl = rd(),a[j].v = rd();
		work();
	}
	return 0;
}