1. 程式人生 > 其它 >【Luogu P1084】[NOIP2012 提高組] 疫情控制

【Luogu P1084】[NOIP2012 提高組] 疫情控制

連結:

洛谷

題目大意:

\(H\) 國有 \(n\) 個城市,這 \(n\) 個城市用 \(n-1\) 條雙向道路相互連通構成一棵樹,\(1\) 號城市是首都,也是樹中的根節點。

\(H\) 國的首都爆發了一種危害性極高的傳染病。當局為了控制疫情,不讓疫情擴散到邊境城市(葉子節點所表示的城市),決定動用軍隊在一些城市建立檢查點,使得從首都到邊境城市的每一條路徑上都至少有一個檢查點,邊境城市也可以建立檢查點。但特別要注意的是,首都是不能建立檢查點的。

現在,在 \(H\) 國的一些城市中已經駐紮有軍隊,且一個城市可以駐紮多個軍隊。一支軍隊可以在有道路連線的城市間移動,並在除首都以外的任意一個城市建立檢查點,且只能在一個城市建立檢查點。一支軍隊經過一條道路從一個城市移動到另一個城市所需要的時間等於道路的長度(單位:小時)。

請問最少需要多少個小時才能控制疫情。注意:不同的軍隊可以同時移動。

正文:

這是一道思路簡單,實現不易的噁心題。

很容易發現兩個性質:

  • 軍隊越往上,貢獻越大。
  • 時間越長,貢獻越大。

所以我們可以二分時間,每次就先讓軍隊只往上走,這個用倍增實現,對於根的某個子樹沒有軍隊的,就把其他有多餘軍隊的調過來。總時間複雜度 \(\mathcal{O}(n\log^2 n)\)

程式碼:

const int N = 5e4 + 10;

inline ll Read()
{
	ll x = 0, f = 1;
	char c = getchar();
	while (c != '-' && (c < '0' || c > '9')) c = getchar();
	if (c == '-') f = -f, c = getchar();
	while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
	return x * f;
}

int n, m;
int a[N];
int head[N], tot;
struct edge
{
	int to, nxt; ll val;
}e[N << 1];
void add(int u, int v, ll w)
{
	e[++tot] = (edge) {v, head[u], w}, head[u] = tot; 
}

int f[N][30], len[N][30], dep[N];
queue <int> q;
void bfs()
{
	q.push(1);
	dep[1] = 1;
	while (!q.empty())
	{
		int u = q.front(); q.pop();
		for (int i = head[u]; i; i = e[i].nxt)
		{
			int v = e[i].to;
			if (v == f[u][0]) continue;
			dep[v] = dep[u] + 1;
			f[v][0] = u;
			len[v][0] = e[i].val;
			for (int j = 1; j <= 25; j++)
				f[v][j] = f[f[v][j - 1]][j - 1],
				len[v][j] = len[v][j - 1] + len[f[v][j - 1]][j - 1];
			q.push(v);
		}
	}
}

struct node
{
	ll val; int id;
	bool operator < (const node &a) const
	{ return val < a.val;}
}c[N];
ll b[N], T[N], nd[N];
bool hav[N], need[N];
bool dfs(int u)
{
	bool flag = 1;
	if (hav[u]) return 0;
	for (int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (v == f[u][0]) continue;
		flag = 0;
		if (dfs(v)) return 1;
	}
	return flag;
}

bool check(ll t)
{
	memset (need, 0, sizeof need);
	memset (hav, 0, sizeof hav);
	memset (nd, 0, sizeof nd);
	memset (T, 0, sizeof T);
	memset (c, 0, sizeof c);
	for (int i = 1; i <= m; i++) b[i] = a[i];
	int k = 0, l = 0, g = 0;
	for (int i = 1; i <= m; i++)
	{
		ll tmp = t;
		for (int j = 25; ~j; j--)
			if (tmp >= len[b[i]][j] && f[b[i]][j] > 1) 
				tmp -= len[b[i]][j], b[i] = f[b[i]][j];
		if (f[b[i]][0] == 1 && tmp >= len[b[i]][0]) 
			c[++k] = (node){tmp - len[b[i]][0], b[i]};
		else hav[b[i]] = 1;
	}
	for (int i = head[1]; i; i = e[i].nxt)
		if (dfs(e[i].to)) 
			need[e[i].to] = 1;
	sort (c + 1, c + k + 1);
	for (int i = 1; i <= k; i++)
		if (need[c[i].id] && c[i].val < len[c[i].id][0])
			need[c[i].id] = 0;
		else T[++l] = c[i].val;
	for (int i = head[1]; i; i = e[i].nxt)
		if (need[e[i].to]) nd[++g] = len[e[i].to][0];
	if (l < g) return 0;
	sort (T + 1, T + 1 + l), sort (nd + 1, nd + 1 + g);
	int i = 1, j = 1;
	for (; i <= l && j <= g; )
		if (T[i] >= nd[j]) i++, j++;
		else i++;
	return j > g;
}

int main()
{
//	freopen(".in", "r", stdin);
//	freopen(".out", "w", stdout);
	n = Read();
	int tmp = 0;
	ll l = 0, r = 0;
	for (int i = 1; i < n; i++)
	{
		int u = Read(), v = Read(); ll w = Read();
		r += w;
		add (u, v, w), add(v, u, w);
		if (u == 1 || v == 1) tmp++;
	}
	bfs();
	m = Read();
	if (tmp > m) {puts("-1"); return 0;} 
	for (int i = 1; i <= m; i++) a[i] = Read();
	while (l < r)
	{
		ll mid = l + r >> 1;
		if (check(mid)) r = mid;
		else l = mid + 1;
	}
	printf ("%lld\n", l);
	return 0;
}