NOIP2012 疫情控制(二分,倍增,贪心)

题意:N个城市组成一棵树,1号节点是首都,也是根节点。M个军队驻扎在某些城市上,军队可移动,从一个点移动到邻接点花费的时间为边权;也可以原地驻扎,但是不能驻扎在首都。求最少的时间,可以将调度军队使得从每个叶子节点到根节点的路径上都有驻军。

这个题目很有意思。最开始我反正打偏了,想了半天的树DP(类似多辆铲雪车),然而发现做树背包的话这数据范围大得不靠谱,所以DP不太科学。淘汰了DP,然后考虑一下贪心,但是没什么很好的贪心策略,因为感觉每个军队都可以漫无目的乱跑。然后是二分判定了,刚开始我差点把这个思路淘汰了,然后我休息了一下,突然觉得好像如果时间上限固定的话每个军队的移动都很有策略性,都可以尽可能往根节点跑。

如果时间上限不足以支撑这个军队到根,他显然是尽量往上面走(实现时要用倍增)。对于其他军队,显然都要留在第二层的节点最优,那么我们先让军队都到根节点,然后贪心地将每个军队剩余的移动时间和二层节点到根的距离匹配一下。这个匹配是重点,我考试的时候乱匹配的,得了30分,重写时参考一下网上的几个思路。

对到达根节点的军队和二层城市分别升序排序,然后枚举军队,i指向军队,j指向城市,i,j都是初始为1且单增,对于当前枚举到的军队i,它显然是剩下的时间最少的军队了,他有两个选择:退回他来自的二层城市(他到根节点的路径就不算了,一定可行),或者去驻扎城市j。只要来自的城市还没被覆盖,一定要让他退回(并不是像网上很多人说的什么不足以返回来自的城市就让他退回,我觉得那种说法没有道理),因为来自的城市若还没被覆盖,就说明它排在城市j后面,距离值比j更大,而这两个城市只能二选一,当然宁可把距离值更小的城市留给后面的军队。当他来自的城市已被覆盖,就应该让他去覆盖城市j,如果能覆盖,则j++,否则j保持不变,让i以后的军队来覆盖j。最后如果能覆盖完,则是可行的。



#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
#define clear(a) memset(a,0,sizeof a)
using namespace std;
const int MAXN = 50010;
int N, M;
struct Node {
	int to; LL d;
	Node *next;
} Edge[MAXN*2], *ecnt=Edge, *adj[MAXN];
void addedge(int a, int b, LL d)
{
	(++ecnt)->to = b;
	ecnt->d  = d;
	ecnt->next = adj[a];
	adj[a] = ecnt;
}
int army[MAXN];
LL f[MAXN][20];//i到2^k祖先的路径长
int g[MAXN][20];//i的第2^k个祖先
int rtson;
LL tot;
int from[MAXN];//i属于哪个二层节点的子树
int sons[MAXN];//i有多少儿子
bool cover[MAXN];
int rch[MAXN];//根节点的儿子们
void predfs(int u)
{
	if (g[u][0] == 1) from[u] = u;
	else from[u] = from[g[u][0]];
	for (Node*p = adj[u]; p; p=p->next)
	{
		if (u==1) {
			++rtson;
			rch[rtson] = p->to;
		}
		if (p->to != g[u][0]) {
			++sons[u];
			f[p->to][0] = p->d;
			g[p->to][0] = u;
			predfs(p->to);
		}
	}
}

bool cancover[MAXN];
//记录一个点被覆盖是否等同于他祖先的二层节点被覆盖,即那个二层节点到i是否只有一条路径
void dfs(int u) {
	cancover[u] = 1;
	if (sons[u] > 1) return;
	for (Node*p = adj[u]; p; p=p->next)
		if (p->to != g[u][0])
			dfs(p->to);
}

LL lim;
int pos[MAXN];//军队的当前位置
LL resdis[MAXN];
void send(int x)
{
	LL travel = lim;
	pos[x] = army[x];
	for (int i = 16; i>=0; --i)
	{
		if (f[pos[x]][i] <= travel && g[pos[x]][i]!=0)
		{
			travel -= f[pos[x]][i];
			pos[x] = g[pos[x]][i];
		}
	}
	resdis[x] = travel;
}
int cnt1, cnt2;
struct ARMY {
	int rdis, from;
	bool operator < (const ARMY&t1) const {
		return rdis < t1.rdis;
	}
} A[MAXN];
struct CITY {
	int d, id;
	bool operator < (const CITY&t1) const {
		return d < t1.d;
	}
} B[MAXN];
bool checkok(LL mid)
{
	clear(cover);
	lim = mid;
	cnt1 = cnt2 = 0;
	int i, j;
	for (i = 1; i<=M; ++i) {
		send(i);
		if (pos[i] != 1) {
			if (cancover[pos[i]])
				cover[from[pos[i]]] = 1;
		}
		else {
			++cnt1;
			A[cnt1].rdis = resdis[i];
			A[cnt1].from = from[army[i]];
		}
	}
	for (i = 1; i<=rtson; ++i)
		if (!cover[rch[i]]) {
			++cnt2;
			B[cnt2].d = f[rch[i]][0];
			B[cnt2].id = rch[i];
		}
	if (cnt1 < cnt2) return 0;
	i = j = 1;
	sort(A+1, A+cnt1+1);
	sort(B+1, B+cnt2+1);
	for (; i<=cnt1; ++i)
	{
		if (!cover[A[i].from])
			cover[A[i].from] = 1;
		else {
			for (; cover[B[j].id] && j<=cnt2; ++j);//跳过被120行的语句覆盖的
			if (B[j].d <= A[i].rdis) cover[B[j].id]=1, ++j;
		}
	}
	for (j = 1; j<=cnt2; ++j)
		if (!cover[B[j].id]) return 0;
	return 1;
}

LL work()
{
	LL l = 0, r = tot, mid;
	while (l+1 < r) {
		mid = (l+r) >> 1;
		if (checkok(mid)) r = mid;
		else l = mid;
	}
	if (checkok(l)) return l;
	return r;
}

int main()
{
	int i, j, a, b;
	LL c;
	scanf("%d", &N);
	for (i = 1; i<N; ++i) {
		scanf("%d%d%lld", &a, &b, &c);
		tot += c;
		addedge(a, b, c);
		addedge(b, a, c);
	}
	scanf("%d", &M);
	predfs(1);
	if (rtson > M) { puts("-1"); return 0; }
	for (i = 1; i<=M; ++i)
		scanf("%d", army+i);
	for (i = 1; i<=rtson; ++i)
		dfs(rch[i]);
	for (i = 1; i<=16; ++i)
		for (j = 1; j<=N; ++j)
		{
			g[j][i] = g[ g[j][i-1] ][i-1];
			f[j][i] = f[j][i-1] + f[ g[j][i-1] ][i-1];
		}
	cout << work() << '\n';
	return 0;
}


你可能感兴趣的:(NOIP2012 疫情控制(二分,倍增,贪心))