NOIP2012 疫情控制

也許更好的閱讀體驗

Description\mathcal{Description}
原題鏈接

set
一句話題意
一個人可以堵住一個子樹,不能一次堵住整棵樹,求堵住每個通往葉子節點的路徑,走的最遠的那個人走的路程最少是多少,若不能堵住輸出1-1
data
Solution\mathcal{Solution}
看了下其他題解,都說很毒瘤
最開始我也認爲很毒瘤
就是在決定一個點是否要跨過根這個地方比較麻煩
但是在碼的途中,突然想到一個無腦的方法
於是寫起來就很愉快了

最…最…,顯然二分路程是多少

再繼續想之前,先看一下我們checkcheck函數要求在什麼複雜度決定
n50000n\leq 50000,可以nlog2nnlog^2n解決,也就是說checkcheck的複雜度只要小於nlognnlogn即可

一個人在二分到的路程內,儘量往上走是最優的
若一個人可以到達根節點,那麼就要考慮其跨過這個根節點去堵其它子樹
顯然,若跨過根節點,那麼走到根節點下面的那個節點就可以了

對於一個可以跨過根節點的人,他可能不要跨過根節點,也可能要跨過根節點
也可能一個子樹裏的人跨過根節點去堵其他子樹,另外的一個子樹的人跨過根節點來堵他的子樹,這是因爲原來這個子樹的人比較靠近根節點,跨過根節點後走的較遠,而他所在子樹與根節點的路徑又比較短
想想好像有點麻煩,想到這頓時覺得這題很毒瘤

但其實,我們不用想那麼多
我們把所有能跨過根節點的人都走到根節點
將其還允許走的路程全部記下來
把所有需要人去堵的子樹找出來,記下其與根節點的距離
顯然,能走得最遠的人去堵距離根節點最遠的子樹是最優的

也就是說要從大到小排序,這個過程我們用一個大根堆維護即可
對於一個到子樹的距離,我們記下其子樹的編號,對於每棵子樹,我們記下其內可以跨過根節點的人中,還允許走的路程最少的路程是多少

再考慮一個人可能不要跨過根節點,我們在彈大根堆時,若發現這棵子樹可以用比當前允許走的路程最大的人更小的人替代,那麼那個人就不需要跨過根節點了
由於那個人仍然在大根堆內,我們把其記在一個隊列裏,表示有哪些路程被提前用了
這樣就是最優的
仍有不懂請看代碼,我覺得還是很無腦的,畢竟什麼特殊的操作都沒有,代碼應該一下就看懂了

Code\mathcal{Code}

/*******************************
Author:Morning_Glory
LANG:C++
Created Time:2019年10月22日 星期二 20時50分59秒
*******************************/
#include <cstdio>
#include <fstream>
#include <queue>
#define ll long long
#define inf 12345678987654
#define mp make_pair
using namespace std;
const int maxn = 500005;
const int maxm = 1000006;
const int lim = 20;
//{{{cin
struct IO{
	template<typename T>
	IO & operator>>(T&res){
		res=0;
		bool flag=false;
		char ch;
		while((ch=getchar())>'9'||ch<'0')	flag|=ch=='-';
		while(ch>='0'&&ch<='9')	res=(res<<1)+(res<<3)+(ch^'0'),ch=getchar();
		if (flag)	res=~res+1;
		return *this;
	}
}cin;
//}}}
int n,m,cnt,u,v,val,num;
int head[maxn],nxt[maxm],to[maxm],w[maxm],hav[maxn],dep[maxn],lg[maxn]={-1};
int fa[maxn][lim];
ll l,r,mid;
ll d[maxn],f[maxn];
bool vis[maxn];
priority_queue <int> qc,qf;
priority_queue < pair<int,int> >qn;
//{{{add
void add (int u,int v,int val)
{
	nxt[++cnt]=head[u],head[u]=cnt,to[cnt]=v,w[cnt]=val;
}
//}}}
//{{{dis
ll dis (int x,int y)
{
	return d[x]>d[y]?d[x]-d[y]:d[y]-d[x];
}
//}}}
//{{{dfs
void dfs (int x)
{
	r=max(r,d[x]);
	for (int i=1;i<=lg[dep[x]];++i)	fa[x][i]=fa[fa[x][i-1]][i-1];

	for (int e=head[x];e;e=nxt[e])
		if (to[e]!=fa[x][0]){
			fa[to[e]][0]=x,dep[to[e]]=dep[x]+1;
			d[to[e]]=d[x]+w[e];
			dfs(to[e]);
		}
}
//}}}
//{{{move
void move (int x,int nm)
{
	ll t=mid;
	for (int i=lg[dep[x]];~i;--i){
		ll ds=dis(fa[x][i],x);
		if (fa[x][i]>1&&t>=ds)	t-=ds,x=fa[x][i];
	}
	if (t>dis(1,x)){
		ll ds=t-dis(1,x);
		f[x]=min(f[x],ds);
		while (nm--)	qc.push(ds);
	}
	else	vis[x]=true;
}
//}}}
//{{{chk
bool chk (int x)
{
	if (vis[x])	return	false;
	bool flag=true;
	for (int e=head[x];e;e=nxt[e])
		if (to[e]!=fa[x][0]){
			flag=false;
			if (chk(to[e]))	return true;
		}
	return flag;
}
//}}}
//{{{check
bool check ()
{
	while (!qc.empty())	qc.pop();
	while (!qn.empty())	qn.pop();
	while (!qf.empty())	qf.pop();
	for (int i=1;i<=n;++i)	vis[i]=0,f[i]=inf;
	for (int i=1;i<=n;++i)
		if (hav[i])	move(i,hav[i]);

	for (int e=head[1];e;e=nxt[e])
		if (chk(to[e]))	qn.push(mp(w[e],to[e]));

	while (!qn.empty()){
		val=qn.top().first,v=qn.top().second,qn.pop();
		while (!qf.empty()&&qf.top()==qc.top())	qf.pop(),qc.pop();

		if (qc.empty())	return false;
		if (qc.top()<val){
			if (f[v]>qc.top())	return false;
			qf.push(f[v]);
		}
		else if (f[v]<=qc.top())	qf.push(f[v]);
		else	qc.pop();
	}
	return true;
}
//}}}
int main()
{
	cin>>n;
	for (int i=1;i<=n;++i)	lg[i]=lg[i>>1]+1;
	for (int i=2;i<=n;++i){
		cin>>u>>v>>val;
		if (u==1||v==1)	++num;
		add(u,v,val),add(v,u,val);
	}
	cin>>m;
	if (m<num)	return printf("-1\n"),0;
	for (int i=1;i<=m;++i) cin>>u,++hav[u];

	dfs(1);

	r<<=1;
	while (l<r){
		mid=(l+r)/2;
		if (check())	r=mid;
		else	l=mid+1;
	}

	printf("%lld\n",l);
	return 0;
}

如有哪裏講得不是很明白或是有錯誤,歡迎指正

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章