HDU 3938 Portal

传送门

无向图,找两点间最长边(一条路径上的)的最小值(所有路径)【其实就是最大找最小,都见了多少次了。。】,每两点u,v都对应这么一个值x(u,v),然后再给你很多个查询,每个查询一个值L问你有多少个x(u,v)小于等于L

x(u,v)必然等于uv路径上某条边的权值。边的权值按从小到大离散分布,那么对L的查询可以退化到最后一个边权小于等于它的边的权值上。

它只问你有多少种两点的x小于等于L,而不关心是哪些点。
这道题很巧妙的地方在于,每条边union后得到一个值,表示这条边加入以后可以让多少个点对从不连通状态变为连通。可以想到,是从小到大添边的,所以这些刚刚连通的点对的x必然等于这条边权值。所以,如果L等于这个权值,那么对L的查询结果包括这条边以及之前所有边带来的值的总和。

由于这道题边权取值范围范围太大,用unordered_map替代数组,一个边权对应一个答案(对该边权查询)。


(19年5月16日)上述在递增序列里找【最后一个小于等于】明显是用二分了。。然而今天才想到二分的两个

!!!!!!!!!!!!非常重要!!!!!!!!!!!!

的细节问题,呵呵,以前都没考虑到(HDU 1257),呵呵

  1. 求解【最后一个】的答案范围是[-1,n-1],求解【第一个】的答案范围是[0,n]
    所以,求解【最后一个】要首先判断序列第一个,求解【第一个】要首先判断序列最后一个。
  2. 求解【最后一个】要int mid = (l + r + 1) >> 1;,这里得+1!!!!!!!!!!
    别问为啥,+1就完事了。
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
#include <unordered_map>
using namespace std;

const int MAXN = 10001;
int N, M, Q;
int pre[MAXN];
unordered_map<int, int> unmap;            // 与用map时间差距不大
int ans;

struct Edge
{
	int n1, n2, w;
	bool operator<(const Edge& e) const
	{
		return w < e.w;
	}
};
vector<Edge> ve;

void init()
{
	ve.clear();
	unmap.clear();
	memset(pre, -1, sizeof pre);
	ans = 0;
}

int f(int x)
{
	if (pre[x] < 0) return x;
	return pre[x] = f(pre[x]);
}

int u(int n1, int n2)
{
	int f1 = f(n1);
	int f2 = f(n2);
	if (f1 != f2)
	{
		int a = -pre[f1];
		int b = -pre[f2];
		if (pre[f1] <= pre[f2])
		{
			pre[f1] += pre[f2];
			pre[f2] = f1;
		}
		else
		{
			pre[f2] += pre[f1];
			pre[f1] = f2;
		}
		return a*b;                 // a*b = (a+b)*(a+b-1)/2 - (a*(a-1)/2 + b*(b-1)/2)  
	}
	return 0;
}

int binary_find(int a, int option)                           // 【最后一个小(-1~n-1)】【第一个大(0~n)】都对应递增序列   
{
	// 1. 最后一个小于等于(前面都满足,之后都不满足)
	if (option == 1)
	{
		if (ve[0].w > a) return -1;

		int l = 0, r = ve.size() - 1;
		for (; l < r;)
		{
			int mid = (l + r + 1) >> 1;                      // 这里得+1,求【最后一个】专用,不然你就死循环了  
			if (ve[mid].w > a) r = mid - 1;
			else l = mid;
		}
		return l;
	}
	// 2. 转化为求解 第一个大于的前一个(前面都不满足,之后都满足)
	else if (option == 2)
	{
		if (ve[ve.size() - 1].w <= a) return ve.size() - 1;  // 这两个“-1”意义不同

		int l = 0, r = ve.size() - 1;
		for (; l < r;)
		{
			int mid = (l + r) >> 1;
			if (ve[mid].w <= a) l = mid + 1;
			else r = mid;
		}
		return l - 1;
	}
}

int main()
{
	int a, b, c;
	for (; ~scanf("%d%d%d", &N, &M, &Q);)
	{
		init();
		for (; M--;)
		{
			scanf("%d%d%d", &a, &b, &c);
			ve.push_back(Edge{ a,b,c });
		}
		sort(ve.begin(), ve.end());

		for (int i = 0; i < ve.size(); i++)
		{
			ans += u(ve[i].n1, ve[i].n2);         // 累加,不会有重复计算
			unmap[ve[i].w] = ans;
		}
		for (; Q--;)
		{
			scanf("%d", &a);
			int t = binary_find(a, 1);            // 在ve中找最后一个权值小于等于a的边的下标  
			printf("%d\n", t == -1 ? 0 : unmap[ve[t].w]);
		}                                         // -1表明查询值比最小的边还小
	}

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值