启发式合并

启发式合并

一般启发式合并

     \;\; 考虑用贡献法来分析。我们令两个集合的分别为 A 和 B,且 |A|<|B|,那么我们把 A 暴力加入到 B 中。那么 A 中的元素所在的集合大小变成 |A|+|B|,也就是说至少变成了原来的两倍。所以每个元素至多被加入 nlogn 次,总的复杂度为 O(nlogn)。

入门例题:

梦幻补丁

题意:

给定长度为n的数组,进行m次操纵。
op == 1 x y:把颜色为x的数组修改为颜色y
op == 2 : 查询此时数组所能形成的段数。

思路:

启发式合并 + 维护链表
发现每种元素只能有一个颜色,且ans是只减不增的。且发现无论是把颜色x—>y,还是颜色y–>x对答案的贡献是一样的。因为两者最后都会成为同一种颜色。考虑启发式合并,将sz[]小的合并到sz[]多的。假设要求要把颜色x–>y,但是sz[y] < sz[x],要把所有颜色为y的合并到颜色为x上,那么下次又要把颜色y的元素涂成颜色z,可以发现此时颜色y的元素已经没有了。优化了这一步,便可实现O(nlogn),考虑如何做到O(1)交换。 只需要维护一个链表 + 映射数组fa[]即可。

code:

const int maxn = 1e6 + 10;
typedef pair <int, int> PII;
int h[maxn], ne[maxn], e[maxn], idx, fa[maxn], ans, sz[maxn], color[maxn];
void add(int u, int v)
{
	e[idx] = v;
	ne[idx] = h[u];
	h[u] = idx ++;
}
void merge(int &a, int &b)// a --- > b
{
	if(sz[a] > sz[b])
	swap(a, b); // a小 
	for(int i = h[a] ; i != -1 ; i = ne[i])
	{
		int son = e[i]; //下标
		ans -= (color[son - 1] == b) + (color[son + 1] == b);
	}
	for(int i = h[a] ; i != -1 ; i = ne[i])
	{
		int son = e[i];
		color[son] = b;
		if(ne[i] == -1)
		{
			ne[i] = h[b], h[b] = h[a], sz[b] += sz[a] ,h[a] = -1, sz[a] = 0;
			break;
		}
	}
}
int main()
{
	int n, m;
	scanf("%d %d", &n, &m);
	memset(h, -1, sizeof(h)), idx = 0;
	for(int i = 1 ; i <= n ; i ++)
	{
		scanf("%d", &color[i]);
		add(color[i], i);
		sz[color[i]] ++;
	}
	ans = 1;
	for(int i = 2 ; i <= n ; i ++)
	{
		if(color[i] != color[i - 1])
		ans ++;
	}
	for(int i = 0 ; i <= 1e6 + 2 ; i ++)
	fa[i] = i;
	for(int i = 1 ; i <= m ; i ++)
	{
		int op;
		scanf("%d", &op);
		if(op == 1)
		{
			int x, y;
			scanf("%d %d", &x, &y);
			if(x == y) continue;
			merge(fa[x], fa[y]);
		}
		else
		{
			printf("%d\n", ans);
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值