【图论专题一】假期计划

【GDOI2015模拟11.22】假期计划

题目

【GDOI2015模拟11.22】假期计划 (Standard IO)
Time Limits: 500 ms Memory Limits: 262144 KB Detailed Limits

Description
航空公司开设了连接着 N 个城市的航班。像任何航线一样,这些城市中的 K 个被设为枢纽。
现在,航空公司提供 M 个单行航班,其中航班 i 从城市 u_i 到城市 v_i 并花 费 d_i 美元。像任何明智的航线一样,对于每一个航班,u_i 和 v_i 中至少一个是 枢纽。两个城市间最多有一个直飞航班,并且没有航班起点与终点为同一城市。
小 X 负责为航空公司运营票务,他收到了 Q 个学生假期的单行航班的请求, 其中第 i 个请求是从城市 a_i 到另一个城市 b_i。
由于小 X 被处理这些票的工作淹没,请帮他计算每个购票请求能否被实现, 以及如果能实现时它的最小花费。
为了减小输出大小,你只要输出可能的购票请求的总数,以及它们总花费的 最小值。

Input
第 1 行:N,M,K 和 Q。
第 2…1+M 行:第 i+1 行包含 u_i,v_i 和 d_i。
第 2+M…1+M+K 行:每行包含一个中枢的编号。
第 M+K+2…M+K+Q+1:每行两个数,表示从 a_i 到 b_i 的购票请求。

Output
第 1 行:能实现的购票请求数。
第 2 行:能实现的购票请求的最小总花费。

Sample Input
3 3 1 2
1 2 10
2 3 10
2 1 5
2
1 3
3 1

Sample Output
1
20

Data Constraint
对于 30%的数据,N<=100,M<=2,000。
对于 100%的数据,1<=N,M<=20,000,1<=K<=200,1<=Q<=50,000, 1<=d_i<=10,000。

Hint
对于第一个航班,唯一可行的路线是 1->2->3,花费 20。

题解

我们观察到N很大,K很小。我们又观察到中转站肯定只能是K个城市中的一个。所以,我们可以以这K个城市的其中一个为起点,跑一遍SPFA。来求出每一个K级城市到其他城市的最短路。于是,基本思路就是通过中间的中转站的相加来求出一个城市到另一个城市的最短距离

第一种方法

我们可以发现,这个算法有一个问题!就是边是单向的。所以我们需要建两个图,一个是读入的图;另一个是将所有的边的向都取反再建一个图。然后再枚举中转站,计算一个城市到另一个城市的最短距离,取一个最小值。就是答案。
预计分数:50分
时间限制是500ms。

第二种方法

时间被卡了。这里可以卡时间!
卡卡就能过
预计分数:50~100分不等

第三种方法

还有一个条件并没有用到,那就是:任意两个航班的终点与起点至少有一个K城市。根据这个,我们发现,这个算法计算了很多没有用的东西,我们貌似只用将中转站确定在与当前起点城市相邻的中转站就行了。这时,好像也不用在重新建图了,这样不就方便快速多了吗?
时间复杂度:O(K*(N+M)+Q)
可以通过全部数据

代码

这里只提供第三种方法的代码

#include<cstdio>
#include<cstring>
#define N 20001
#define M 20001
#define K 201
#define Q 50001
#define d_i 10001
using namespace std;
int bz[N],head1[M*2],next1[M*2],edge1[M*2],v1[M*2],p[K],h[N*2],dis[K][N]; 
int total2,total1,n,m,k,q;
void insert1(int x,int y,int z)
{
	total1++;
	next1[total1]=head1[x];
	head1[x]=total1;
	edge1[total1]=y;
	v1[total1]=z;
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&k,&q);
	for (int i=1;i<=m;i++)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		insert1(x,y,z);
	}	
	memset(bz,0,sizeof(bz));
	for (int i=1;i<=k;i++)
	{
		scanf("%d",&p[i]);
		bz[p[i]]=i;
	}
	for (int i=1;i<=k;i++)
	{
		for (int j=1;j<=n;j++)
		{
			dis[i][j]=99999999;
		}
		dis[i][p[i]]=0;
		int l=1;
		int r=1;
		h[l]=p[i];
		while (l<=r)
		{
			int u=h[l];
			for (int j=head1[u];j;j=next1[j])
			{
				int y=edge1[j];
				if (dis[i][y]>dis[i][u]+v1[j])
				{
					dis[i][y]=dis[i][u]+v1[j];
					r++;
					h[r]=y;
				}
			}
			l++;
		}
	}
	long long total_1=0;
	long long total_2=0;
	for (int i=1;i<=q;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		int min=99999999; 
		if (bz[x]!=0)
		{
			min=dis[bz[x]][y];
		}
		else
		{
			for (int j=head1[x];j;j=next1[j])
			{
				int y1=edge1[j];
				if (min>dis[bz[y1]][y]+v1[j])
				{
					min=dis[bz[y1]][y]+v1[j];
				}
			}
		}
		if (min>=9999999) {/*printf("-1\n");*/continue;}
		total_1++;
		total_2+=min;
		//printf("%d\n",min);
	}
	printf("%d\n",total_1);
	printf("%d\n",total_2);
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值