1. 程式人生 > 實用技巧 >#zkw線段樹,掃描線,dp,離散#NOIP2020.9.26模擬speike

#zkw線段樹,掃描線,dp,離散#NOIP2020.9.26模擬speike


分析

由於可以走邊界,那麼最短路徑一定按橫座標遞增並且經過矩形的頂點,
考慮掃描線,找到當前線段(矩形右邊界可以忽略)兩個端點離的最近而又可達的線段,
dp一下並用線段樹維護就可以了


程式碼

#include <cstdio>
#include <cctype>
#include <algorithm>
#define rr register
using namespace std;
const int N=500011;
typedef long long lll; lll dp[N][2];
struct duan{int x,l,r;}line[N];
int w[N<<2],xt,q,b[N<<1],bas,n,m;
inline signed iut(){
	rr int ans=0,f=1; rr char c=getchar();
	while (!isdigit(c)) f=(c=='-')?-f:f,c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans*f;
}
bool cmp(duan x,duan y){return x.x<y.x;}
inline signed max(int a,int b){return a>b?a:b;}
inline lll min(lll a,lll b){return a<b?a:b;}
inline signed aabs(int x){return x<0?-x:x;}
inline signed query(int x){
	rr int ans=1;
	for (x+=bas;x;x>>=1)
	    ans=max(ans,w[x]);
	return ans;
}
inline void update(int l,int r,int z){
	for(l+=bas-1,r+=bas+1;l^r^1;l>>=1,r>>=1){
		if (!(l&1)) w[l^1]=max(w[l^1],z);
		if (r&1) w[r^1]=max(w[r^1],z);
	}
}
signed main(){
	freopen("speike.in","r",stdin);
	freopen("speike.out","w",stdout);
	n=iut(),m=1,line[1]=(duan){0,0,0},
	line[2]=(duan){xt=iut(),0,0},q=2;
	for (rr int i=1;i<=n;++i){
		rr int lx=iut(),ly=iut(),rx=iut(),ry=iut();
		if (lx>rx) swap(lx,rx); if (ly>ry) swap(ly,ry);
		line[++q]=(duan){lx,ly,ry},b[++m]=ly,b[++m]=ry;
	}
	sort(b+1,b+1+m),m=unique(b+1,b+1+m)-b-1;
	sort(line+2,line+1+q,cmp);
	for (bas=1;(bas<<=1)<m+3;);
	for (rr int i=1;i<=q;++i)
		line[i].l=lower_bound(b+1,b+1+m,line[i].l)-b,
		    line[i].r=lower_bound(b+1,b+1+m,line[i].r)-b;
	for (rr int i=2;i<=q;++i){
		rr int t1=query(line[i].l),t2=query(line[i].r);
		dp[i][0]=min(dp[t1][0]+aabs(b[line[i].l]-b[line[t1].l]),dp[t1][1]+aabs(b[line[i].l]-b[line[t1].r])),
		dp[i][1]=min(dp[t2][0]+aabs(b[line[i].r]-b[line[t2].l]),dp[t2][1]+aabs(b[line[i].r]-b[line[t2].r])),
		update(line[i].l,line[i].r,i);
	}
	return !printf("%lld",min(dp[q][0],dp[q][1])+xt);
}