題解 洛谷 P5465 【[PKUSC2018]星際穿越】
阿新 • • 發佈:2020-07-18
首先考慮題目的性質,發現點向區間連的邊為雙向邊,所以也就可以從一個點向右跳到區間包含該點的點,如圖所示:
但事實上向後跳其實是不優的,可以有更好的方法來節省花費:
因此我們發現一個點跳到其前一個區間的花費為 \(1\),且在跳躍過程中不會向右跳,同時我們還證明了一個點向左的花費單調遞增。
但是從起點進行第一步跳躍時,有可能會向後跳:
其通過向後跳來到達一個更大的包含該點的區間,然後使下一步跳躍到達一個更向前的位置,第一步採取向後跳方案的花費為 \(2\)。
發現只有第一步是特殊的,所以單獨來考慮第一步的情況。
設 \(pos_i=\min\limits_{j=i}^n l_j\),即 \(l_i\)
對每個位置建可持久化線段樹,線段樹中對應的值為該位置不考慮第一步的花費,位置 \(i\) 的線段樹從位置 \(pos_i\) 轉移過來,然後在區間 \([1,i-1]\) 通過標記永久化來實現區間加一,表示不是第一步跳的花費。
查詢時只需在 \(l_i\) 所對應的線段樹上查詢區間 \([l,min(r,l_x-1)]\) 的和,其為位置 \(i\) 不是第一步的總花費,然後再加上第一步花費的貢獻即可。
\(code:\)
#include<bits/stdc++.h> #define maxn 300010 #define maxm 10000010 #define mid ((l+r)>>1) using namespace std; typedef long long ll; template<typename T> inline void read(T &x) { x=0;char c=getchar();bool flag=false; while(!isdigit(c)){if(c=='-')flag=true;c=getchar();} while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();} if(flag)x=-x; } int n,q,tot; int a[maxn],pos[maxn],rt[maxn],ls[maxm],rs[maxm]; ll sum[maxm],add[maxm]; ll gcd(ll a,ll b) { return b?gcd(b,a%b):a; } void modify(int L,int R,int l,int r,int &cur) { int x=++tot; ls[x]=ls[cur],rs[x]=rs[cur],add[x]=add[cur]; sum[x]=sum[cur]+(min(R,r)-max(L,l)+1),cur=x; if(L<=l&&R>=r) { add[cur]++; return; } if(L<=mid) modify(L,R,l,mid,ls[cur]); if(R>mid) modify(L,R,mid+1,r,rs[cur]); } ll query(int L,int R,int l,int r,int cur) { if(L>R) return 0; if(L<=l&&R>=r) return sum[cur]; ll v=add[cur]*(min(R,r)-max(L,l)+1); if(L<=mid) v+=query(L,R,l,mid,ls[cur]); if(R>mid) v+=query(L,R,mid+1,r,rs[cur]); return v; } int main() { read(n); for(int i=2;i<=n;++i) read(a[i]),pos[i]=a[i]; for(int i=n-1;i>=2;--i) pos[i]=min(pos[i],pos[i+1]); for(int i=2;i<=n;++i) rt[i]=rt[pos[i]],modify(1,i-1,1,n,rt[i]); read(q); while(q--) { int l,r,x; ll g,v; read(l),read(r),read(x),v=r-l+1; v+=query(l,min(r,a[x]-1),1,n,rt[a[x]]); g=gcd(v,r-l+1),printf("%lld/%lld\n",v/g,(r-l+1)/g); } return 0; }