LOJ6435 & 洛谷5465 「PKUSC2018」星際穿越 倍增

題目鏈接:
loj6435
洛谷5465

蒟蒻zyd:這不是大水題嗎?看我寫個O(nlog2n)O(nlog^2n)的詭異ST表卡卡常數跑過去
題目:輸出區間距離和
蒟蒻zyd:(笑容逐漸消失)

沒想到一道倍增題能這麼巧(毒)妙(瘤)……

一些奇奇怪怪的性質

這裏是需要用到的性質……
爲了方便,這裏把“花費1單位時間進行傳送”稱爲“走了1步”qwq
Ps.一些類似的情況就不畫圖了……我不會說其實是我懶QWQ

性質1

假設起始點是ss,如果當前走了tt步,能到達的最左端的點是xx,那麼一定能在tt步內從ss到達[x,s1][x,s-1]內的任意一點。

證明:如果某個點ppxp<sx\le p <s)不能到達,因爲某個點向左連接的點是連續的一段,所以ss能到的點中的ll的最小值一定>p>p(否則就珂以到pp了qwq),所以無法從ss能到的點中的任意一點走到pp左邊,就矛盾了qwq
在這裏插入圖片描述
爲什麼能在tt步內到達呢?類似地,比如要t+1t+1步才能到pp,那麼也不能在tt步內到達pp左邊(不然從tt步內走到pp左邊的點走到pp即可qwq),矛盾qwq
所以得證。

性質2

假設起點是ss,終點是xx,那麼ssxx的最短路只能是這兩種之一:
1.一直向左走
2.一開始先從ss向右走一步,再一直向左走。
換言之,如果要向右走,只能一開始向右一步,其他時候就只能向左走了。
先證明不存在從ss向右走兩步的情況:
ss走兩次到xx,走一次到yy。若存在l[x]<l[y]l[x]<l[y],即向右走兩步比向右走一步更優的情況:
(如圖所示,藍邊是xxl[x]l[x]連的邊)
在這裏插入圖片描述
由於一個點向左連的邊的編號是連續的一段,所以ss一定有一條邊連向xx,因此ss能一步就到xx,因此更優。
(如圖所示,ss能通過紅邊一步到達xx
在這裏插入圖片描述
若不存在l[x]<l[y]l[x]<l[y]的情況,那沒必要走兩步到xx了qwq,直接到yy然後向左走就珂以了qwq

再證明不能中途向右走的情況:
設起點ss向右走一步能到達的最右邊的點是r(x)r(x),現在走到了xx,如果要向右走,那麼到達的點也一定在[x+1,r(s)][x+1,r(s)]範圍內。
如果走到的點>r(x)>r(x),假設這個點爲yy,那麼l[y]l[y]一定比ss小,所以ss珂以向右走一步到達yy,但y>r(x)y>r(x),矛盾,所以xx向右走能到達的點在[x+1,r(s)][x+1,r(s)]範圍內qwq
根據性質1,向右走一步不會讓答案更優,所以就不用往右走了qwq
(其實性質1只證明了[x,s][x,s]範圍內不會使答案更優,但是腦補一下也珂以明白在[s+1,r(s)][s+1,r(s)]範圍內也成立qwq)

綜上,如果要向右走,那隻能一開始向右走一步。

Ps. 有一個很顯然的性質我感覺不用證明……就不把它列到上面正經證明的性質了awa
即:如果前tt步能到達的範圍是[L,R][L,R],那麼第t+1t+1不能到達的範圍是min{l[i]},L<=i<=Rmin\{l[i]\},L<=i<=R

題目解析

倍增:
f[i][j]f[i][j]表示從ii開始,先向右一次,再向左2j2^j次所能到達的最左端的點。
那麼f[i][j]=f[f[i][j1]][j1]f[i][j]=f[f[i][j-1]][j-1]
因爲由性質2,中途向右走不會使答案更優,所以從f[i][j1]f[i][j-1]向左2j12^{j-1}步即爲f[i][j]f[i][j]
顯然向左走x+1x+1步會比向左xx步走得遠,所以倍增數組是單調的。
查詢就搞一個sum[i][j]sum[i][j],表示ii[f[i][j],i][f[i][j],i]的點的距離和qwq
然後大莉統計即珂(走出2i2^i步的貢獻分兩個部分統計)
具體見代碼qwq

毒瘤代碼

#include<stdio.h>
#include<cstring>
#include<algorithm>
#define re register int
using namespace std;
typedef long long ll;
int read() {
	re x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=(x<<1)+(x<<3)+ch-'0';
		ch=getchar();
	}
	return x*f;
}
inline void write(const int x) {
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
const int Size=300005;
const int INF=0x3f3f3f3f;
int n,l[Size],LOG[Size],f[Size][21],sum[Size][21];
int Calc(int x,int pos) {
	if(l[pos]<=x)	return pos-x;
	int ans=pos-l[pos];		//先向左跳一次 
	pos=l[pos];
	int cnt=1;
	//跳到再跳一次就<x的位置 
	for(re i=LOG[pos]; i>=0; i--) {
		if(f[pos][i]>x) {
			//f[pos][i]~pos到pos的距離和+pos到原點的距離*這段區間的個數 
			ans+=sum[pos][i]+(pos-f[pos][i])*cnt;
			pos=f[pos][i];
			cnt+=1<<i;
		}
	}
	return ans+(pos-x)*(cnt+1);
}
int main() {
	n=read();
	LOG[0]=-1;
	LOG[1]=0;
	for(re i=2; i<=n; i++) {
		l[i]=read();
		LOG[i]=LOG[i>>1]+1;
	}
	f[n+1][0]=INF;
	for(re i=n; i; i--) {
		f[i][0]=min(f[i+1][0],l[i]);
		sum[i][0]=i-f[i][0];		//跳一步就珂以,所以距離和爲區間點數 
	}
	for(re j=1; j<=18; j++) {
		for(re i=1<<j; i<=n; i++) {
			if(f[i][j-1]) {
				f[i][j]=f[f[i][j-1]][j-1];
				sum[i][j]=sum[i][j-1]+sum[f[i][j-1]][j-1]+((f[i][j-1]-f[i][j])<<(j-1));
			}
		}
	}
	int q=read();
	while(q--) {
		int l=read();
		int r=read();
		int x=read();
		int p=Calc(l,x)-Calc(r+1,x);
		int q=r-l+1;
		int k=__gcd(p,q);
		printf("%d/%d\n",p/k,q/k);
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章