The 15th Chinese Northeast Collegiate Programming Contest - K.City

题意

给出 n n n个点, m m m条无向边, q q q个询问.

每条边有权值 w w w, 每次询问删除所有权值小于 p p p的边后, 图中剩余的所有节点中, 能相互到达的节点对数.

思路

考虑用并查集。有两个问题:

1. 1. 1.并查集只能加边,不能减边。

2. 2. 2.数据的范围很大,如果我们每做一次判断都要进行一次遍历的话时间复杂度肯定会超时。

对于问题1:题目给的是破坏的多少条边,反过来经过排序之后逆向思维可以理解成查询的值变小了又加入了多少条边,这样我们就可以用并查集来进行处理了。

对于问题2:我们可以离散化 + + +离线询问,对查询的值由大到小排序(如果我们按照从小到大排序, 那么我们初始情况应认为 m m m条边都存在图中, 每次我们要删除小于 p i p_i pi的边. 由于并查集的删除操作并不好实现, 因此我们考虑从大到小排序)

最后思考 a n s ans ans:如果一个集合里面有 n n n个元素,那么就有 1 + 2 + 3 + … + n − 1 1+2+3+…+n-1 1+2+3++n1个城市对。两个集合合并的话不能进行一个简单的相加,因为会有重复,意思是我们之前这两个集合各自的城市对已经加入 a n s ans ans了,合并之后又加一遍会有数据重复。所以我们可以先记录原先两个集合的节点数量 a , b a,b a,b,及合成后集合的节点的数量 s u m sum sum,新增加的节点对数就是 s u m × ( s u m − 1 ) / 2 − a × ( a − 1 ) / 2 − b × ( b − 1 ) / 2 sum\times (sum-1)/2 - a\times (a-1)/2 - b\times (b-1)/2 sum×(sum1)/2a×(a1)/2b×(b1)/2

#include <iostream>
#include <algorithm>
#include <map>
using namespace std;
const int maxn = 2e5 + 100;
struct infor
{
	int x, y;
	int cost;
} e[maxn];
struct lian
{
	long long sum;
	int f;
} fa[maxn];
struct qs
{
	long long v;
	int id;
} se[maxn];
bool cmp1(infor x, infor y)
{
	return x.cost > y.cost;
}
bool cmpc(qs x, qs y)
{
	return x.v > y.v;
}
int find(int x)
{
	if (x == fa[x].f)
		return x;
	fa[x].f = find(fa[x].f);
	return fa[x].f;
}
void solve()
{
	long long cnt[maxn];
	int n, m, q,i,j=0;
	scanf("%d%d%d", &n, &m, &q);
	for (i = 1; i <= n; i++)
	{
		fa[i].sum = 1;
		fa[i].f = i;
	}
	for (i = 0; i < m; i++)
		scanf("%d%d%d", &e[i].x, &e[i].y, &e[i].cost);
	for (i = 0; i < q; i++)
	{
		scanf("%lld", &se[i].v);
		se[i].id = i;
	}
	sort(e, e + m, cmp1);
	sort(se, se + q, cmpc);
	long long ans = 0;
	for (i = 0; i < q; i++)
	{
		for (j; j < m; j++)
		{
			int from = e[j].x;
			int to = e[j].y;
			int c = e[j].cost;
			if (c < se[i].v)
				break;
			from = find(from);
			to = find(to);
			if (from != to)
			{
				long long a, b;
				a = fa[from].sum;
				b = fa[to].sum;
				fa[from].f = to;
				fa[to].sum += a;
				ans += fa[to].sum * (fa[to].sum - 1) / 2 - a * (a - 1) / 2 - b * (b - 1) / 2;
			}
		}
		cnt[se[i].id] = ans;
	}
	for (i = 0; i < q; i++)
		printf("%lld\n", cnt[i]);
}
int main()
{
	int t;
	cin >> t;
	while (t--)
	{
		solve();
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值