1. 程式人生 > >[JZOJ5906]【NOIP2018模擬10.15】傳送門

[JZOJ5906]【NOIP2018模擬10.15】傳送門

Description

給你一棵n個點的帶邊權無向樹。
需要從1號點開始,將整棵樹遍歷一遍並最終回到1號點。
走到一個點時,可以在這個點設定一個傳送門,只要地圖上有兩個傳送門,就可以在它們之間以0的時間傳送
如果要在當前節點設定傳送門,並且已經有了兩個傳送門,那麼需要選擇之前的某一個傳送門讓他消失,也就是說任意時刻至多有兩個傳送門,且不能在同一節點。

求最小總時間。
n<=1000000

Solution

不妨以1為根。

首先我們可以肯定的是,最優走法一定是在祖先設定好了傳送門,在某個後代遍歷完了以後傳回來。

如果沒有傳送門,時間顯然是邊權*2

有結論:如果當前節點有傳送門,從它的某個子樹中傳了回來,再次走入這個子樹一定不會更優。

換句話說,一個傳送門的某個子樹只有遍歷完了才會傳回來。
證明:
考慮我們為什麼要重新走進去,那一定是我們傳送回來的地方和我們還沒遍歷的地方在某個後代分叉了。

那麼我們大可以直接將當前傳送門設在分叉的位置,再暴力走回來,這樣分叉到當前傳送點路徑都是兩遍,而分叉以下的部分都是1遍。

這樣我們就可以DP了
G [ i ]

G[i] 為遍歷完i為根的子樹,不用傳送門的時間(就是邊權*2)
F [ i ] F[i] 為可以用傳送門的最優時間

考慮怎麼轉移F
假設我們當前節點為i,DP完的兒子是p
要麼在當前節點設傳送門,暴力走p,再傳回來,很明顯我們會從最深的那個葉子傳回來。
要麼在p的子樹中設傳送門,暴力走這條邊2次(下去1次,回來1次)
因此 F

[ i ] = p s o n [ i ] m i n ( G [ p ] + l e n g t h [ i ] [ p ] d e e p e s t [ p ] , 2 l e n g t h [ i ] [ p ] + F [ p ] ) F[i]=\sum\limits_{p\in son[i]}min(G[p]+length[i][p]-deepest[p],2*length[i][p]+F[p])
deepest[i]表示i子樹中到i最遠的葉子。

最後答案就是F[1]

Code

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
#define N 1000005
#define LL long long
using namespace std;
int pr[2*N],dt[2*N],fs[N],n,m,nt[2*N];
LL f[N],g[N],des[N];
void link(int x,int y,int z)
{
	nt[++m]=fs[x];
	dt[fs[x]=m]=y;
	pr[m]=z;
}
void dfs(int k,int fa)
{
	f[k]=g[k]=0;
	LL v=0;
	for(int i=fs[k];i;i=nt[i])
	{
		int p=dt[i];
		if(p!=fa)
		{
			dfs(p,k);
			g[k]=g[k]+2*pr[i]+g[p];
			f[k]=f[k]+min(2*pr[i]+f[p],g[p]+pr[i]-des[p]);
			des[k]=max(des[k],des[p]+pr[i]);
		}
	}
	//f[k]=min(f[k],g[k]-des[k]);
}
int main()
{
	cin>>n;
	fo(i,1,n-1)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		link(x,y,z),link(y,x,z);
	}
	dfs(1,0);
	printf("%lld\n",f[1]);
}