[BZOJ4651][Noi2016]网格(Tarjan 求割点)

Address

Solution

  • 下面把跳蚤视为白点,蛐蛐视为黑点
  • 可以发现答案只可能是 − 1 -1 1 0 0 0 1 1 1 2 2 2 四种
  • 然后随机输出一个,你可以以 1 / (4 ^ T) 的高概率通过此题
  • 易得, 如果没有黑点,则有三种情况
  • (1) n × m ≤ 2 n\times m\le 2 n×m2 − 1 -1 1
  • (2) n = 1 n=1 n=1 m = 1 m=1 m=1 1 1 1
  • (3) ELSE \text{ELSE} ELSE 2 2 2
  • 此外,如果白点不超过 2 2 2 个且所有白点连通,则输出 − 1 -1 1
  • 然后我们发现坐标系巨大,考虑缩小图的规模
  • 一个想法是把所有黑点周围 3 × 3 3\times 3 3×3 的点加入,但存在一个反例
.....
...*.
.....
.*...
.....
  • 上图中,星号为黑点,点号为白点
  • 如果只加入黑点周围 3 × 3 3\times 3 3×3 的点,那么 ( 3 , 3 ) (3,3) (3,3) 位置处的白点会成为割点,而这显然是不对的
  • 所以需要把所有黑点周围 5 × 5 5\times 5 5×5 的点加入(即与该黑点横纵坐标之差的绝对值都不超过 2 2 2 的所有点)
  • 于是我们的图规模缩小到了 25 c 25c 25c
  • 然后就能判断连通性了。如果加入的所有点构成的连通块中,存在一个连通块,使得这个连通块内存在两个白点不连通,则图不连通。输出 0 0 0
  • 然后判断答案是否为 1 1 1 ,对图的每个连通块求割点,如果有割点则答案为 1 1 1 ,否则为 2 2 2
  • 注意一个小细节:对于一个加入的白点,如果这个白点周围 3 × 3 3\times3 3×3 区域没有黑点,那么这个点显然是不能成为割点的,注意特判
  • 复杂度 O ( ∑ c ) O(\sum c) O(c) ,常数 100+

Code

#include <bits/stdc++.h>

// 20030830

inline int read()
{
	int res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	return bo ? ~res + 1 : res;
}

template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}

const int N = 25e5 + 5, M = 1e7 + 5, dx[] = {-1, 1, 0, 0},
dy[] = {0, 0, -1, 1}, ZZQ = 9999991;

typedef long long ll;

namespace Hash
{
	int ecnt, nxt[N], adj[ZZQ], tot[N]; ll val[N];
	
	void clean()
	{
		while (ecnt--) adj[val[ecnt + 1] % ZZQ] = 0;
		ecnt = 0;
	}
	
	void ins(ll v, int t)
	{
		int u = v % ZZQ;
		nxt[++ecnt] = adj[u]; adj[u] = ecnt; tot[ecnt] = t;
		val[ecnt] = v;
	}
	
	int query(ll v)
	{
		int u = v % ZZQ;
		for (int e = adj[u]; e; e = nxt[e])
			if (val[e] == v) return tot[e];
		return 0;
	}
};

int n, m, c, X[N], Y[N], tot, col[N], ort[N][4], fa[N], ecnt, nxt[M],
adj[N], go[M], dfn[N], low[N], times, sze;
bool wf[N], havecut;

void add_edge(int u, int v)
{
	nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
	nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u;
}

ll which(int x, int y)
{
	return 1ll * (x - 1) * m + y;
}

int cx(int x)
{
	if (fa[x] != x) fa[x] = cx(fa[x]);
	return fa[x];
}

void zm(int x, int y)
{
	int ix = cx(x), iy = cx(y);
	if (ix != iy) fa[iy] = ix;
}

void dfs(int u, int rt)
{
	sze++;
	int tql = 0;
	dfn[u] = low[u] = ++times;
	for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
		if (!dfn[v])
		{
			dfs(v, rt);
			tql++;
			if (((u == rt && tql > 1) || (u != rt && dfn[u] <= low[v]))
				&& wf[u]) havecut = 1;
			low[u] = Min(low[u], low[v]);
		}
		else low[u] = Min(low[u], dfn[v]);
}

void work()
{
	Hash::clean();
	int x, y;
	n = read(); m = read(); c = read();
	if (!c) return (void) puts(1ll * n * m <= 2 ? "-1" :
		(n == 1 || m == 1 ? "1" : "2"));
	tot = 0;
	for (int i = 1; i <= c; i++)
		X[i] = read(), Y[i] = read(), Hash::ins(which(X[i], Y[i]), ++tot),
		col[tot] = 1, wf[tot] = 0;
	for (int i = 1; i <= c; i++)
		for (int a = -2; a <= 2; a++)
			for (int b = -2; b <= 2; b++)
			{
				int tx = X[i] + a, ty = Y[i] + b;
				if (tx < 1 || tx > n || ty < 1 || ty > m) continue;
				ll p = which(tx, ty);
				if (!Hash::query(p)) Hash::ins(p, ++tot),
					X[tot] = tx, Y[tot] = ty, col[tot] = 0, wf[tot] = 0;
				wf[Hash::query(p)] |= abs(a) <= 1 && abs(b) <= 1;
			}
	for (int i = 1; i <= tot; i++) fa[i] = i;
	for (int i = 1; i <= tot; i++)
		for (int k = 0; k < 4; k++)
		{
			int x = X[i] + dx[k], y = Y[i] + dy[k];
			if (x < 1 || x > n || y < 1 || y > m)
			{
				ort[i][k] = 0; continue;
			}
			ort[i][k] = Hash::query(which(x, y));
			if (ort[i][k]) zm(i, ort[i][k]);
		}
	int ac = 0;
	for (int i = 1; i <= tot; i++) ac += cx(i) == i;
	for (int i = 1; i <= tot; i++) fa[i] = i;
	for (int i = 1; i <= tot; i++) if (!col[i])
		for (int k = 0; k < 4; k++)
		{
			int j = ort[i][k]; if (!j) continue;
			if (!col[j]) zm(i, j);
		}
	int wa = 0;
	for (int i = 1; i <= tot; i++) wa += !col[i] && cx(i) == i;
	if (ac < wa) return (void) puts("0");
	if (1ll * n * m - c <= 2) return (void) puts("-1");
	ecnt = times = 0;
	for (int i = 1; i <= tot; i++) adj[i] = dfn[i] = 0;
	for (int i = 1; i <= tot; i++) if (!col[i])
		for (int k = 0; k < 4; k += 2)
		{
			int j = ort[i][k]; if (!j) continue;
			if (!col[j]) add_edge(i, j);
		}
	havecut = 0;
	for (int i = 1; i <= tot; i++)
		if (sze = 0, !col[i] && !dfn[i] && (dfs(i, i), havecut || sze == 2))
			return (void) puts("1");
	puts("2");
}

int main()
{
	int T = read();
	while (T--) work();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值