[APIO2019]桥梁(分块+并查集)

题目:洛谷P5443LOJ#3145

题目描述:

给定\(n\)个点,\(m\)条边,初始每条边都有边权,\(q\)次操作

每次操作为以下两种之一:

  • 修改:改变一条边的边权
  • 询问:求以某个点为起始点,只能经过大小不小于某个值的边,求能到达多少个不同的点

\(n \leq 50000\)\(m,q \leq 100000\),所有的边权长度不超过\(10^{9}\)

蒟蒻题解:

法一:

修改就直接修改,询问时把所有边权大于等于给定值的边取出来跑并查集

时间复杂度\(\Theta(m \cdot q \cdot α(m))\)

期望得分:\(13\)

法二:

离线,由于每次询问都是经过大小不小于某个值的边,所以可以把所有的操作按大到小排序,对于永远不会被修改的边可以直接添加到并查集里

每次处理询问时,就找所有可能会被修改的边加进可撤销并查集中,处理完这个询问后再撤销

时间复杂度\(\Theta(m log\ m + q^{2} log\ m)\)

期望得分:\(13\)

法三:

法一的优点是询问小时很快,法二的优点是修改小时很快

考虑结合以上两个算法的优点,对操作进行分块,设每个块的大小为\(S\)

对于一个块,把所有的操作按大到小排序,对于这个块内永远不会被修改的边可以直接添加到并查集里,然后每次处理询问时,就找所有可能会被修改的边加进可撤销并查集中,处理完这个询问后再撤销(同法二)

时间复杂度\(\Theta(\frac{q}{S} \cdot (m log\ m + S^{2} log\ m)) = \Theta(\frac{q \cdot m log\ m}{S} + q \cdot S \cdot log\ m)\)

考虑进一步优化,由于每次修改的边数不超过\(S\),对于不会被修改的边用的并查集用路径压缩,然后会被修改的边直接在图中连边,后面可以直接暴力\(dfs\)去找,遍历到的点数一定不超过\(S\)

时间复杂度\(\Theta(\frac{q}{S} \cdot (m log\ m + S^{2} α(m))) = \Theta(\frac{q \cdot m log\ m}{S} + q \cdot S \cdot α(m))\)

\(\frac{q \cdot m log\ m}{S} = q \cdot S \cdot α(m)\),解得\(S = \sqrt{\frac{m log\ m}{α(m)}}\)

然后我就试着取了\(S = \sqrt{m log\ m}\),结果洛谷开只有\(12\)分,\(LOJ\)\(O2\)只有\(73\)分(开\(Ofast\)好像也一样)代码自带大常数\kk

后面看了一下别人程序取的块的大小,看到有人取\(\sqrt{10q}\),我就试了下,结果在\(LOJ\)\(Ofast\)后过了,洛谷开\(O2\)后也过了(但是洛谷不开\(O2\)只有\(8\)分)

参考程序:

#include<bits/stdc++.h>
using namespace std;
#define Re register int

const int N = 50005, M = 100005;
struct info
{
	int l, r, s, id;
}a[M], qu[M], d[M];
int n, m, q, S = 1, res, t, cnt, tot, g[N], fa[N], siz[N], ans[N], ds[M], tim[M], hea[N], nxt[N], to[N];
bool b[M], p[N];

inline int read()
{
	char c = getchar();
	int ans = 0;
	while (c < 48 || c > 57) c = getchar();
	while (c >= 48 && c <= 57) ans = (ans << 3) + (ans << 1) + (c ^ 48), c = getchar();
	return ans;
}

inline void write(int x)
{
	int num = 0;
	char sc[15];
	while (x) sc[++num] = x % 10 + 48, x /= 10;
	while (num) putchar(sc[num--]);
	putchar('\n');
}

inline bool cmp(info x, info y)
{
	return x.s > y.s;
}

inline bool cmp1(info x, info y)
{
	return x.id < y.id;
}

inline int find_fa(int x)
{
	return (fa[x] ^ x) ? fa[x] = find_fa(fa[x]) : x;
}

inline void add(int x, int y)
{
	nxt[++cnt] = hea[x], to[cnt] = y, hea[x] = cnt;
}

inline void dfs(int x)
{
	p[x] = 1, tot += siz[x];
	for (Re i = hea[x]; i; i = nxt[i])
	{
		int u = to[i];
		if (!p[u]) dfs(u);
	}
}

inline void clea(int x)
{
	p[x] = 0;
	for (Re i = hea[x]; i; i = nxt[i])
	{
		int u = to[i];
		if (p[u]) clea(u);
	}
}

int main()
{
	n = read(), m = read();
	for (Re i = 1; i <= m; ++i) a[i].l = read(), a[i].r = read(), a[i].s = read(), a[i].id = i;
	q = read(), S = min(q, (int)sqrt(q * 10));
	for (Re i = 1, j; i <= q; i = j + 1)
	{
		j = min(i + S, q), res = t = 0;
		int u = j - i + 1;
		for (Re k = 1; k <= n; ++k) fa[k] = k, siz[k] = 1;
		for (Re k = 0; k < u; ++k)
		{
			qu[k].l = read(), qu[k].r = read(), qu[k].s = read(), qu[k].id = k;
			if (qu[k].l & 1) b[qu[k].r] = 1;
		}
		for (Re k = 1; k <= m; ++k)
			if (!b[k]) d[res].l = a[k].l, d[res].r = a[k].r, d[res++].s = a[k].s;
			else g[t++] = k;
		sort(qu, qu + u, cmp), sort(d, d + res, cmp);
		for (Re k = 0, l = 0; k < u; ++k)
		{
			while (l < res && d[l].s >= qu[k].s)
			{
				int v = find_fa(d[l].l), w = find_fa(d[l].r);
				if (v ^ w) siz[v] += siz[w], fa[w] = v;
				++l;
			}
			if (qu[k].l & 1) continue;
			cnt = tot = 0;
			for (Re r = 0; r < t; ++r) ds[g[r]] = a[g[r]].s, tim[g[r]] = -1;
			for (Re r = 0; r < u; ++r)
				if ((qu[r].l & 1) && qu[r].id < qu[k].id && qu[r].id > tim[qu[r].r]) ds[qu[r].r] = qu[r].s, tim[qu[r].r] = qu[r].id;
			for (Re r = 0; r < t; ++r)
				if (ds[g[r]] >= qu[k].s) add(find_fa(a[g[r]].l), find_fa(a[g[r]].r)), add(fa[a[g[r]].r], fa[a[g[r]].l]);
			dfs(find_fa(qu[k].r)), clea(fa[qu[k].r]);
			for (Re r = 0; r < t; ++r)
				if (ds[g[r]] >= qu[k].s) hea[fa[a[g[r]].l]] = hea[fa[a[g[r]].r]] = 0;
			ans[qu[k].id] = tot;
		}
		sort(qu, qu + u, cmp1);
		for (Re k = 0; k < u; ++k)
			if (qu[k].l & 1) a[qu[k].r].s = qu[k].s, b[qu[k].r] = 0;
			else write(ans[k]);
	}
	return 0;
}
posted @ 2021-05-01 00:02  clfzs  阅读(133)  评论(0)    收藏  举报