【二分】电话网络

描述 Description
    由于地震使得连接汶川县城的电话线全部损坏,加入你是负责讲电话先接到震中汶川县城的负责人,汶川县城周围分布N(1<=n<=1000)根按1…n电话线杆间可以拉电话线,其余的由于地震使得无法被连接。
    第i对电话线杆的两个端点分别为Ai,Ri,它们间的距离为Li(1<=li<=1000000)。数据中保证每对最多只出现一次。编号为1的电话线杆已经接入了全国的电话网络,整个县城的电话线全部连到了编号为N的电话线杆上。也就是说,你的任务仅仅是找一条将1号和N号电话线杆连起来的路径,其余的电话线杆并不一定要连入电话网络。
    电信公司决定支援灾区免费为汶川县城连接K(0<=k<n)对有你指定的电话线杆。对于此外的那些电话线,需要为它们付费,总费用等于其中最长的电话线长度(每根电话线仅连接一对电话线杆)。如果需要连接的电话线杆不超过K对,那么总支出为0.
    请你计算一下,将电话线引到震中汶川县城最少需要在电话线上花多少钱?
输入格式 Input Format
    输入文件的第一行包含三个用空格隔开的整数:N P和K。
    第二行到第P+1行:每行分别都为三个用空格隔开的整数:Ai Bi和Li。
输出格式 Output Format
    输出文件仅包含一个整数,表示在这项工程上的最小支出。如果任务不可能完成,则输出-1.

样例输入 Sample Input [ 复制数据]
样例输出 Sample Output [ 复制数据]
时间限制 Time Limitation

各个测试点1s


这道题比较好,因为不能用常规的二分思路,否则肯定超时。


普通的思路就是二分出最长的长度,然后搜索检验,n<=1000,果断超时,加之我一开始二分的时候,是二分的[minlen,maxlen]的连续区间,更加慢。若先排序再二分有可能的集合,速度略有优化。仍然过不了。


正确的方法是二分出第K+1长的边,前K长的边都可以直接免费,短于该边的边,因为不影响结果,所以可以不考虑它的长度。

而我们的验证,即是否存在方案,使我们二分出的长度为(第K+1)长或(小于K+1)长。明显去数长度大于二分长度的边最少有多少条!

于是采用最短路!先把长度小于等于二分长度的边临时设为0,大于二分长度的边临时设为1,从1到n求最短路,求出的结果就是长度大于二分长度的边的条数。


#include <cstdio>
#include <string>
#include <cstring>
#include <algorithm>
using std::sort;

long mid;
bool used[1010];
long map0[1010][1010];
long map[1010][1010];
long dist[1010];
long c[3010000];

long que[2000010];
long n, k;
const long qmod = 2000000;

long getint()
{
	long rs=0;bool sgn=1;char tmp;
	do tmp = getchar();
	while (!isdigit(tmp)&&tmp-'-');
	if (tmp == '-'){tmp=getchar();sgn=0;}
	do rs=(rs<<3)+(rs<<1)+tmp-'0';
	while (isdigit(tmp=getchar()));
	return sgn?rs:-rs;	
}

void spfa()
{
	memset(dist,0x3f,sizeof dist);
	long l = 0;
	long r = 0;
	r ++;
	que[r] = 1;
	dist[1] = 0;
	while (l < r)
	{
		l ++;
		if (l == qmod)
			l = 0;
		long u = que[l];
		used[u] = false;
		for (long v=1;v<n+1;v++)
		{
			if (map0[u][v]<0x3f3f3f3f && dist[v]>dist[u]+map[u][v])
			{
				dist[v] = dist[u] + map[u][v];
				if (!used[v])
				{
					used[v] = true;
					r ++;
					if (r == qmod)
						r = 0;
					que[r] = v;
				}
			}
		}
	}
}


bool can()
{
	for (long i=1;i<n+1;i++)
	{
		for (long j=i;j<n+1;j++)
		{
			if (map0[i][j] == 0x3f3f3f3f)
				map[i][j] = 0x3f3f3f3f;
			else if (map0[i][j] <= mid)
				map[i][j] = map[j][i] = 0;
			else
				map[i][j] = map[j][i] = 1;
		}
	}
	spfa();
	if (dist[n] > k)
		return false;
	else
		return true;
}

bool check()
{
	for (long i=1;i<n+1;i++)
		for (long j=i;j<n+1;j++)
			map[i][j] = map[j][i] = 1;
	spfa();
	return dist[n] < 0x3f3f3f3f;
}

int main()
{
	freopen("phone.in","r",stdin);
	freopen("phone.out","w",stdout);

	n = getint();
	long p = getint();
	k = getint();

	memset(map0,0x3f,sizeof map0);
	for (long i=1;i<p+1;i++)
	{
		long a = getint();
		long b = getint();
		long cc = getint();
		map0[a][b] = map0[b][a] = cc;
		c[++c[0]] = cc;
	}

	sort(c+1,c+1+c[0]);
	long tmp = 1;
	for (long i=2;i<c[0]+1;i++)
		if (c[i]!=c[tmp])
			c[++tmp] = c[i];
	c[0] = 0;
	long l = 0;
	long r = tmp;

	long ans = 0x3f3f3f3f;

	if (!check())
	{
		printf("-1");
		return 0;
	}

	while (l <= r)
	{
		long mm = (l+r)>>1;
		mid = c[mm];
		if (can())
		{
			if (ans > mid)
				ans = mid;
			r = mm-1;
		}
		else
		{
			l = mm+1;
		}
	}

	printf("%ld",ans);
	
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值