1. 程式人生 > >[Noi2014]購票 BZOJ3672 點分治+斜率優化+CDQ分治

[Noi2014]購票 BZOJ3672 點分治+斜率優化+CDQ分治

input CP using rar 暴力 out def #define bubuko

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 技術分享圖片

對於所有測試數據,保證 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

技術分享圖片

分析:

先考慮一下暴力...其實前3個點的分還是很好拿的...稍微搞一搞就出來了,n^2DP比較顯然...

後面的呢,應該拿50分沒有什麽大問題的說...一條鏈的斜率優化實在不能再好寫一點了...

附上30分的暴力:

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <queue>
#include <cstdlib>
using namespace std;
#define N 200005
#define ll long long
struct node{int to,next;}e[N<<1];
int fa[N],n,head[N],cnt;
ll f[N],dep[N],l[N],p[N],q[N],a[N];
void add(int x,int y){e[cnt]=(node){y,head[x]};head[x]=cnt++;}
void dfs(int x,int from)
{
    fa[x]=from;dep[x]=dep[from]+a[x];
    for(int t=fa[x];t&&dep[x]-dep[t]<=l[x];t=fa[t])f[x]=min(f[t]+(dep[x]-dep[t])*p[x]+q[x],f[x]);
    for(int i=head[x];i!=-1;i=e[i].next)if(e[i].to!=from)dfs(e[i].to,x);
}
int main()
{
    scanf("%d%*d",&n);memset(head,-1,sizeof(head));memset(f,0x3f,sizeof(f));
    for(int i=2;i<=n;i++)
    {
        scanf("%d%lld%lld%lld%lld",&fa[i],&a[i],&p[i],&q[i],&l[i]);
        add(fa[i],i);add(i,fa[i]);
    }f[1]=0;dfs(1,0);
    for(int i=2;i<=n;i++)printf("%lld\n",f[i]);return 0;
}

目測我似乎能跑過4個點...反正就是這個樣子的暴力...

f[x]=max{f[j]+(dep[x]-dep[j])*p[x]+q[x]};(j是x的祖先,滿足dep[x]-dep[j]<=l[x])

顯然...發現dep單調,p[x]不單調,但這不是問題用不著寫什麽奇怪的東西,每次二分導函數即可...

但是,這個題出在樹上就讓人很崩潰了...顯然,我們考慮有兩種斜率優化的方法可以選擇。

(1)動態維護凸包+二分查找 → 如果你會可持久化凸包的話可以考慮考慮,至於樹剖+動態凸包能不能過...反正時間復雜度有問題...nlog^3n實在是不忍直視...

(2)CDQ分治(前置技能:貨幣兌換)每次考慮如何維護凸包用來更新節點,那麽考慮選擇一個鏈更新一個點的子樹,那麽這樣的話,只需要維護一個凸包就可以了。這種情況下,滿足直接二分轉移一下就可以了。那麽如何選擇這個點,顯然,我們可以選擇樹的重心,因為這樣的話,滿足子樹的最大一個小於n/2,轉移次數<=logn(點分治),每次遍歷滿,時間復雜度O(nlog^2n)。

可以考慮,如果一個點,滿足所有的父親節點都被確定,那麽這個點就可以被確定,並且可以用這條鏈更新這個點的子樹,那麽我們每次找到重心之後,先找到它的父節點那部分作為CDQ分治的左部分,用左部分更新重心的子樹並將其更新,之後再遞歸處理其他的子部分。而每次可以發現,我們dep是單調遞增的,維護一個凸包,之後按照子樹中節點能夠更新的最深的位置進行排名,每次進行二分導函數,找到最優解(同任務安排),其實也就是把樹分治當做CDQ分治來做。

附上代碼:

#include <cstdio>
#include <cmath>
#include <iostream>
#include <queue>
#include <algorithm>
#include <cstring>
#include <cstdlib>
#include <bitset>
using namespace std;
#define N 200005
#define ll long long
#define K(x) (-dep[x])
#define B(x) (f[x])
#define Y(x,y) (K(x)*p[y]+B(x))
struct egde{int to,next;}e[N<<1];
int head[N],cnt,fa[N],siz[N],sn,rot,mx[N],vis[N],q[N],lx[N],ln,n,rx[N],rn,tail;
ll dep[N],p[N],l[N],f[N],b[N];
void add(int x,int y){e[cnt]=(egde){y,head[x]};head[x]=cnt++;}
void get_root(int x,int from)
{
	siz[x]=1,mx[x]=0;
	for(int i=head[x];i!=-1;i=e[i].next)
	{
		int to1=e[i].to;
		if(to1!=from&&!vis[to1])
		{
			get_root(to1,x);
			siz[x]+=siz[to1];
			mx[x]=max(siz[to1],mx[x]);
		}
	}
	mx[x]=max(mx[x],sn-siz[x]);
	if(mx[x]<mx[rot])rot=x;
}
bool cmp(int i,int j,int k)
{
	long double t1=(long double)(K(k)-K(i))*(B(k)-B(j));
	long double t2=(long double)(K(k)-K(j))*(B(k)-B(i));
	return t1>=t2-1e-10;
}
bool cmp1(const int &a,const int &b){return dep[a]-l[a]>dep[b]-l[b];}
void get_rx(int x,int from)
{
	if(from!=0)rx[++rn]=x;
	for(int i=head[x];i!=-1;i=e[i].next)
	{
		int to1=e[i].to;
		if(to1!=from&&!vis[to1])get_rx(to1,x);
	}
}
void Update(int x)
{
	if(!tail)return ;int l=1,r=tail;
	while(l<r)
	{
		int m=(l+r)>>1;
		if(Y(q[m],x)>Y(q[m+1],x))l=m+1;
		else r=m;
	}
	f[x]=min(f[x],Y(q[l],x)+b[x]);
}
void dfs(int x)
{
	int rt;
	sn=siz[x],rot=0,get_root(x,0),rt=rot,vis[rt]=1;
	// printf("%d %d\n",x,rt);
	if(x!=rt)siz[x]-=siz[rt],dfs(x);
	tail=ln=rn=0;lx[++ln]=rt;
	for(int i=rt;i!=x;i=fa[i])
	{
		if(dep[rt]-l[rt]<=dep[fa[i]])f[rt]=min(f[rt],Y(fa[i],rt)+b[rt]);
		lx[++ln]=fa[i];
	}
	get_rx(rt,0);sort(rx+1,rx+rn+1,cmp1);
	// if(x==1)for(int i=1;i<=rn;i++)printf("%d %d\n",rt,rx[i]);
	int j=1;
	for(int i=1;i<=ln&&j<=rn;i++)
	{
		while(j<=rn&&dep[lx[i]]<dep[rx[j]]-l[rx[j]])Update(rx[j++]);
		while(tail>1&&cmp(q[tail],q[tail-1],lx[i]))tail--;
		q[++tail]=lx[i];
	}while(j<=rn)Update(rx[j++]);
	for(int i=head[rt];i!=-1;i=e[i].next)
	{
		int to1=e[i].to;
		if(!vis[to1])dfs(to1);
	}
}
int main()
{
	scanf("%d%*d",&n);memset(f,0x3f,sizeof(f));memset(head,-1,sizeof(head));
	for(int i=2;i<=n;i++)
	{
		scanf("%d%lld%lld%lld%lld",&fa[i],&dep[i],&p[i],&b[i],&l[i]);
		dep[i]+=dep[fa[i]];b[i]+=dep[i]*p[i];add(fa[i],i);add(i,fa[i]);
		// printf("%d\n",b[i]);
	}f[1]=0;mx[0]=1<<30,siz[1]=n;dfs(1);
	for(int i=2;i<=n;i++)printf("%lld\n",f[i]);return 0;
}

  

[Noi2014]購票 BZOJ3672 點分治+斜率優化+CDQ分治