题目链接
相邻格子不同为连通,计算每个点所在的连通块大小。
想法
我采用了并查集的做法。
开一个辅助数组记录连通块大小,每次合并的时候更新父亲节点的大小即可。
一个点先与它上面的点判定,若判定连通则加入上方点所属的块中。
再与左边的点判定,若连通则再将两个块合并。
总体复杂度O(n3),其中并查集不加优化复杂度为O(n),使用路径压缩+按节点大小合并优化并查集,将并查集复杂度降低为近似O(1),总体复杂度近似为O(n2)。
并查集优化
路径压缩
每次查找的时候,将路径上的所有儿子的父亲改写为最原始的祖宗。这样下次就可以直接找到祖宗。
实现:
inline int find(const int &x)
{
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
通俗写法:
inline int find(const int &x)
{
if(fa[x] == x)
return x;
fa[x] = find(fa[x]);
return fa[x];
}
按秩合并
思考:
有2个块需要合并,那么将小块接在大块的后面能够让整体复杂度更优。
可以将每个祖宗及其所有儿子看做一棵树,那么节点到根的距离就是查找父亲需要的次数。
将小树并入大树,可以避免树的深度过深。
这个rnk数组保存的是树的深度的上界。
通常写法:
const int maxn = 10000000;
int rnk[maxn],fa[maxn];
inline void unite(const int &x,const int &y)
{
int t1 = find(x),t2 = find(y);
if(t1 == t2)
return;
if(rnk[t1] == rnk[t2])
{
fa[t1] = t2;
++rnk[t2];
}
else if(rnk[t1] > rnk[t2])
fa[t2] = t1;
else
fa[t1] = t2;
}
总结
路径压缩和按秩合并都能将并查集复杂度降为O(logn),而两者一起使用能够降为O(log∗n)。
n |
log∗n |
(−∞, 1] |
0 |
(1, 2] |
1 |
(2, 4] |
2 |
(4, 16] |
3 |
(16, 65536] |
4 |
(65536, 2^65536] |
5 |
(数据转自链接,个人认为这篇博文讲并查集讲得相当不错。)
代码
因为按秩合并需要额外数组,而在此数量级下开数组的消耗可能比优化更大……
于是借用了题目中维护的“连通块大小”,即节点个数,进行了按size合并的优化。
#include <cstdio>
using namespace std;
#define getId(x, y) (((x - 1) * n) + y)
int fa[1001000];
int h[1001000];
char all[2][1010];
int n, m, i, j;
inline char nc()
{
static char buf[400000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 400000, stdin), p1 == p2) ? EOF : *p1++;
}
void read(char *s)
{
static char c;
for (c = nc(); c != '1' && c != '0'; c = nc());
for (; c == '0' || c == '1'; *++s = c, c = nc());
}
void read(int &r)
{
static char c; r = 0;
for (c = nc(); c > '9' || c < '0'; c = nc());
for (; c >= '0' && c <= '9'; r = (r << 1) + (r << 3) + (c ^ 48), c = nc());
}
inline int find(const int &x)
{
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
inline void unite(const int &a, const int &b)
{
int t1 = find(a), t2 = find(b);
if (t1 == t2)
return;
h[t1] > h[t2] ? fa[t2] = t1, h[t1] += h[t2] : fa[t1] = t2, h[t2] += h[t1];
}
int main()
{
read(n);
read(m);
int n2 = n * n;
for (int i = 1; i <= n2; ++i)
fa[i] = i, h[i] = 1;
int now = 0, pre = 1;
i = 1;
read(all[now]);
for (j = 2; j <= n; ++j)
if (all[now][j] != all[now][j - 1])
unite(getId(i, j), getId(i, j - 1));
for (i = 2; i <= n; ++i)
{
now ^= 1;
pre ^= 1;
read(all[now]);
if (all[now][1] != all[pre][1])
unite(getId(i, 1), getId(i - 1, 1));
for (j = 2; j <= n; ++j)
{
if (all[now][j] != all[now][j - 1])
unite(getId(i, j), getId(i, j - 1));
if (all[now][j] != all[pre][j])
unite(getId(i, j), getId(i - 1, j));
}
}
int x, y;
for (i = 1; i <= m; ++i)
{
read(x);
read(y);
printf("%d
", h[find(getId(x,y))]);
}
return 0;
}