BZOJ3672/UOJ7 [Noi2014]購票


本文版權歸ljh2000和博客園共有,歡迎轉載,但須保留此聲明,並給出原文鏈接,謝謝合作。

 

 

本文作者:ljh2000
作者博客:http://www.cnblogs.com/ljh2000-jump/
轉載請注明出處,侵權必究,保留最終解釋權!

 

Description

   今年夏天,NOI在SZ市迎來了她30周歲的生日。來自全國 n 個城市的OIer們都會從各地出發,到SZ市參加這次盛會。
       全國的城市構成了一棵以SZ市為根的有根樹,每個城市與它的父親用道路連接。為了方便起見,我們將全國的 n 個城市用 1 到 n 的整數編號。其中SZ市的編號為 1。對於除SZ市之外的任意一個城市 v,我們給出了它在這棵樹上的父親城市 fv  以及到父親城市道路的長度 sv
從城市 v 前往SZ市的方法為:選擇城市 v 的一個祖先 a,支付購票的費用,乘坐交通工具到達 a。再選擇城市 a 的一個祖先 b,支付費用並到達 b。以此類推,直至到達SZ市。
對於任意一個城市 v,我們會給出一個交通工具的距離限制 lv。對於城市 v 的祖先 a,只有當它們之間所有道路的總長度不超過 lv  時,從城市 v 才可以通過一次購票到達城市 a,否則不能通過一次購票到達。對於每個城市 v,我們還會給出兩個非負整數 pv,qv  作為票價參數。若城市 v 到城市 a 所有道路的總長度為 d,那么從城市 v 到城市 a 購買的票價為 dpv+qv
每個城市的OIer都希望自己到達SZ市時,用於購票的總資金最少。你的任務就是,告訴每個城市的OIer他們所花的最少資金是多少。
 

Input

第 1 行包含2個非負整數 n,t,分別表示城市的個數和數據類型(其意義將在后面提到)。輸入文件的第 2 到 n 行,每行描述一個除SZ之外的城市。其中第 v 行包含 5 個非負整數 f_v,s_v,p_v,q_v,l_v,分別表示城市 v 的父親城市,它到父親城市道路的長度,票價的兩個參數和距離限制。請注意:輸入不包含編號為 1 的SZ市,第 2 行到第 n 行分別描述的是城市 2 到城市 n。

Output

輸出包含 n-1 行,每行包含一個整數。其中第 v 行表示從城市 v+1 出發,到達SZ市最少的購票費用。同樣請注意:輸出不包含編號為 1 的SZ市。

 

Sample Input

7 3
1 2 20 0 3
1 5 10 100 5
2 4 10 10 10
2 9 1 100 10
3 5 20 100 10
4 4 20 0 10

Sample Output


40
150
70
149
300
150

HINT

 

 

對於所有測試數據,保證 0≤pv≤106,0≤qv≤1012,1≤fv<v;保證 0<sv≤lv≤2×1011,且任意城市到SZ市的總路程長度不超過 2×1011


輸入的 t 表示數據類型,0≤t<4,其中:


當 t=0 或 2 時,對輸入的所有城市 v,都有 fv=v-1,即所有城市構成一個以SZ市為終點的鏈;


當 t=0 或 1 時,對輸入的所有城市 v,都有 lv=2×1011,即沒有移動的距離限制,每個城市都能到達它的所有祖先;


當 t=3 時,數據沒有特殊性質。


n=2×10^5

 

 

正解:CDQ分治+斜率優化DP+樹分治

解題報告:

  這道題是三算法合一經典好題...

  我們考慮如果是在一個序列上,就是simple的斜率優化裸題了。但是P[i]的變化不隨i變化而規律性變化,所以我每次要求的最優斜率是不一樣的,維護好下凸包,每次在凸包上二分找到最優值即可。

  對於樹上的距離限制,我們肯定對於每個點要重建凸包,如果每次重建顯然復雜度無法承受,所以我們需要改變節點的處理順序,使得祖先上的點順次加入並且不用重構,這很簡單,只要考慮每個點能被最上面的哪些點更新到,排個序就可以掃一遍做完了。

  另外樹分治是為了保證我整體上的處理復雜度是log次,CDQ分治的最大精髓就是先遞歸處理一部分,再用處理完的部分來更新未處理的部分,顯然當祖先節點遞歸處理完之后,就可以用來處理子樹,而每棵子樹內部的點都共用了一條鏈上的祖先,更新答案,最后再遞歸處理就可以了。

  說的有點抽象,看看代碼理解起來還是挺快的。其實是我懶,不想寫公式... 

 

 

//It is made by ljh2000
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long LL;
const int MAXN = 200011;
const LL inf = (1LL<<62);
int n,f[MAXN],ecnt,first[MAXN],to[MAXN],next[MAXN],size[MAXN],maxS[MAXN],cnt,dui[MAXN];
LL p[MAXN],q[MAXN],lim[MAXN],dis[MAXN],w[MAXN],dp[MAXN];
bool use[MAXN];
struct node{ LL val; int id; }a[MAXN];
inline bool cmp(node q,node qq){ return q.val>qq.val; }
inline void link(int x,int y,LL z){ next[++ecnt]=first[x]; first[x]=ecnt; to[ecnt]=y; w[ecnt]=z; }
inline LL upd(int i,int j){ return dp[j]+(dis[i]-dis[j])*p[i]+q[i]; }
inline LL K(int x,int y){ return (dp[y]-dp[x])/(dis[y]-dis[x]); }
inline int getint(){
    int w=0,q=0; char c=getchar(); while((c<'0'||c>'9') && c!='-') c=getchar();
    if(c=='-') q=1,c=getchar(); while (c>='0'&&c<='9') w=w*10+c-'0',c=getchar(); return q?-w:w;
}

inline LL getLL(){
    LL w=0; LL q=0; char c=getchar(); while((c<'0'||c>'9') && c!='-') c=getchar();
    if(c=='-') q=1,c=getchar(); while (c>='0'&&c<='9') w=w*10+c-'0',c=getchar(); return q?-w:w;
}

inline void dfs(int x){//預處理每個節點的dis和size
	size[x]=1;
	for(int i=first[x];i;i=next[i]) {
		dis[to[i]]=dis[x]+w[i];
		dfs(to[i]); size[x]+=size[to[i]];
	}
}

inline void find_root(int x,int S,int &rt){  
	maxS[x]=0; size[x]=1;
	for(int i=first[x];i;i=next[i]) {
		int v=to[i]; if(use[v]) continue;
		find_root(v,S,rt);
		size[x]+=size[v]; maxS[x]=max(maxS[x],size[v]);
	}
	maxS[x]=max(maxS[x],S-size[x]);
	if(maxS[x]<maxS[rt] && size[x]>1/*不然會GG!!!*/) rt=x;
}

inline void dfs2(int x){
	a[++cnt].id=x; a[cnt].val=dis[x]-lim[x];
	for(int i=first[x];i;i=next[i]) if(!use[to[i]]) dfs2(to[i]);
}

inline void solve(int x,int S){
	if(S==1) return ; int rt=0,now;
	find_root(x,S,rt);
	for(int i=first[rt];i;i=next[i]) use[to[i]]=1;//把重心的兒子節點堵上
	//CDQ分治
	//先處理除了重心的子樹之外的部分
	solve(x,S-size[rt]+1);//重心也放入這個部分,方便以后討論

	cnt=0; for(int i=first[rt];i;i=next[i]) dfs2(to[i]);
	sort(a+1,a+cnt+1,cmp);//把子樹內所有點按能被更新到的最高高度自大往小排序
	now=rt;//需要向外更新的是,重心到當前分治根節點的這條鏈上的所有點
	int tail,l,r,mid,pos; tail=0;
	for(int i=1;i<=cnt;i++) {
		while(now!=f[x] && dis[a[i].id]-lim[a[i].id]<=dis[now]) {
			while(tail>1 && K(dui[tail],now)>=K(dui[tail-1],dui[tail])) tail--;
			//因為dis[now]遞減,相當於是橫坐標遞減,可以看做是倒着加入,注意斜率判斷方向
			dui[++tail]=now; now=f[now];
		}
		if(tail>0) {
			//在凸包上二分求得最優解
			l=1; r=tail; pos=1;
			while(l<=r) {
				mid=(l+r)>>1; if(mid==tail) { pos=tail; break; }
				if(K(dui[mid],dui[mid+1])>=p[a[i].id]) l=mid+1,pos=mid+1;//當前最優的應該是mid+1!!!
				else r=mid-1;
			}
			dp[a[i].id]=min(dp[a[i].id] , upd(a[i].id,dui[pos]));
		}
	}
	for(int i=first[rt];i;i=next[i]) solve(to[i],size[to[i]]);
}

inline void work(){
	n=getint(); LL x; x=getint();
	for(int i=2;i<=n;i++) {
		f[i]=getint(); x=getLL(); link(f[i],i,x);
		p[i]=getint(); q[i]=getLL(); lim[i]=getLL();
	}
	dfs(1); maxS[0]=n+1; for(int i=2;i<=n;i++) dp[i]=inf;
	solve(1,size[1]);
	for(int i=2;i<=n;i++) printf("%lld\n",dp[i]);
}

int main()
{
    work();
    return 0;
}

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM