一名苦逼的OIer,想成为ACMer

Iowa_Battleship

BZOJ1912或洛谷3629 [APIO2010]巡逻

一道树的直径

BZOJ原题链接

洛谷原题链接

显然在原图上路线的总长为\(2(n-1)\)
添加第一条边时,显然会形成一个环,而这条环上的所有边全部只需要走一遍。所以为了使添加的边的贡献最大化,我们找出树的直径,将其两端点连上边即可。
设直径长\(L\),于是路线总长就变为\(2(n-1)-L+1=2n-L-1\)
\(K=1\)时,这就是答案。
\(K=2\)时,我们考虑在上述添边后图中再添一条边。
添加这条边同样会形成一个环,如果这个环与之前的环没有边重合的话,那么贡献和上一边一样,但如果有重边,就会导致重边又需要走两边。
这就相当于添加的这条边在重边上的贡献为\(-1\),所以我们可以将第一次添边时搜到的直径上的所有边的权值改为\(-1\),然后依旧找出修改后的图的直径即可。
设第一条直径长\(L_1\),第二条长\(L_2\),那么最终路线总长就变为\(2(n-1)-L_1+1-L_2+1=2n-L_1-L_2\)
另外,注意因为第二次找直径时,有边的权值为负,这时普通的\(dfs\)找直径是无法找到正确的直径的。

#include<cstdio>
using namespace std;
const int N = 1e5 + 10;
int di[N << 1], da[N << 1], ne[N << 1], fi[N], dis[N], D[N], dia, l;
inline int re()
{
	int x = 0;
	char c = getchar();
	bool p = 0;
	for (; c<'0' || c>'9'; c = getchar())
		p |= c == '-';
	for (; c >= '0'&&c <= '9'; c = getchar())
		x = x * 10 + (c - '0');
	return p ? -x : x;
}
inline void add(int x, int y)
{
	di[++l] = y;
	da[l] = 1;
	ne[l] = fi[x];
	fi[x] = l;
}
inline int maxn(int x, int y)
{
	return x > y ? x : y;
}
void dfs(int x, int fa, int d)
{
	int i, y;
	if (dia < d)
	{
		dia = d;
		D[0] = x;
	}
	D[x] = fa;
	for (i = fi[x]; i; i = ne[i])
	{
		y = di[i];
		if (y != fa)
			dfs(y, x, d + 1);
	}
}
void fixda(int x, int y)
{
	int i;
	for (i = fi[x]; i; i = ne[i])
		if (di[i] == y)
		{
			da[i] = -1;
			return;
		}
}
void dp(int x, int fa)
{
	int i, y;
	for (i = fi[x]; i; i = ne[i])
	{
		y = di[i];
		if (y != fa)
		{
			dp(y, x);
			dis[0] = maxn(dis[0], dis[x] + dis[y] + da[i]);
			dis[x] = maxn(dis[x], dis[y] + da[i]);
		}
	}
}
int main()
{
	int i, n, m, x, y;
	n = re();
	m = re();
	for (i = 1; i < n; i++)
	{
		x = re();
		y = re();
		add(x, y);
		add(y, x);
	}
	dfs(1, 0, 0);
	dia = 0;
	dfs(D[0], 0, 0);
	if (m == 1)
	{
		printf("%d", (n << 1) - dia - 1);
		return 0;
	}
	for (x = D[0]; x; x = y)
	{
		y = D[x];
		fixda(x, y);
		fixda(y, x);
	}
	dp(1, 0);
	printf("%d", (n << 1) - dia - dis[0]);
	return 0;
}

posted on 2018-09-02 16:32  Iowa_Battleship  阅读(118)  评论(0编辑  收藏  举报

导航