一名苦逼的OIer,想成为ACMer

Iowa_Battleship

BZOJ1015或洛谷1197 [JSOI2008]星球大战

BZOJ原题链接

洛谷原题链接

发现正着想毫无思路,所以我们可以考虑倒着思考,把摧毁变成建造。
这样很容易想到用并查集来维护连通块,问题也变的很简单了。
建原图,先遍历一遍所有边,若某条边的两端点未被摧毁,那么合并两个点,再倒着去枚举被摧毁的点,对于一个点遍历它的边,若是未摧毁的点,那么就用并查集将两个连通块合并,并记录答案即可。

#include<cstdio>
using namespace std;
const int N = 4e5 + 10;
struct eg {
	int x, y;
};
eg a[N];
int fi[N], di[N], ne[N], fa[N], an[N], br[N], l;
bool v[N];
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;
	ne[l] = fi[x];
	fi[x] = l;
}
int fin(int x)
{
	if (!(fa[x] ^ x))
		return x;
	return fa[x] = fin(fa[x]);
}
int main()
{
	int i, j, n, m, x, y, k, s;
	n = re();
	m = re();
	for (i = 1; i <= m; i++)
	{
		a[i].x = re();
		a[i].y = re();
		add(a[i].x, a[i].y);
		add(a[i].y, a[i].x);
	}
	for (i = 1; i <= n; i++)
		fa[i] = i;
	k = re();
	for (i = 1; i <= k; i++)
	{
		br[i] = re();
		v[br[i]] = 1;
	}
	s = n - k;
	for (i = 1; i <= m; i++)
		if (!v[a[i].x] && !v[a[i].y])
		{
			x = fin(a[i].x);
			y = fin(a[i].y);
			if (x ^ y)
			{
				fa[x] = y;
				s--;
			}
		}
	an[k] = s;
	for (i = k; i; i--)
	{
		v[x = br[i]] = 0;
		s++;
		for (j = fi[x]; j; j = ne[j])
			if (!v[y = di[j]])
			{
				x = fin(x);
				y = fin(y);
				if (x ^ y)
				{
					fa[x] = y;
					s--;
				}
			}
		an[i - 1] = s;
	}
	for (i = 0; i <= k; i++)
		printf("%d\n", an[i]);
	return 0;
}

posted on 2018-11-06 09:00  Iowa_Battleship  阅读(107)  评论(0编辑  收藏  举报

导航