假期计划

假期计划 ⁡ \operatorname{假期计划}

题目链接: j z o j   3936 jzoj\ 3936 jzoj 3936

题目

航空公司开设了连接着 N N N 个城市的航班。像任何航线一样,这些城市中的 K K K 个被设为枢纽。

现在,航空公司提供 M M M 个单行航班,其中航班 i i i 从城市 u i u_i ui 到城市 v i v_i vi 并花费 d i d_i di 美元。像任何明智的航线一样,对于每一个航班, u i u_i ui v i v_i vi 中至少一个是 枢纽。两个城市间最多有一个直飞航班,并且没有航班起点与终点为同一城市。

小 X 负责为航空公司运营票务,他收到了 Q Q Q 个学生假期的单行航班的请求, 其中第 i i i 个请求是从城市 a i a_i ai 到另一个城市 b i b_i bi

由于小 X 被处理这些票的工作淹没,请帮他计算每个购票请求能否被实现, 以及如果能实现时它的最小花费。

为了减小输出大小,你只要输出可能的购票请求的总数,以及它们总花费的 最小值。

输入

1 1 1 行: N , M , K N,M,K N,M,K Q Q Q
2..1 + M 2..1+M 2..1+M 行:第 i + 1 i+1 i+1 行包含 u i , v i u_i,v_i ui,vi d i d_i di
2 + M . . 1 + M + K 2+M..1+M+K 2+M..1+M+K 行:每行包含一个中枢的编号。
M + K + 2.. M + K + Q + 1 M+K+2..M+K+Q+1 M+K+2..M+K+Q+1 行:每行两个数,表示从 a i a_i ai b i b_i bi 的购票请求。

输出

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

样例输入

3 3 1 2
1 2 10
2 3 10
2 1 5
2
1 3
3 1

样例输出

1
20

样例解释

对于第一个航班,唯一可行的路线是 1 −  ⁣ ⁣ > 2 −  ⁣ ⁣ > 3 1-\!\!>2-\!\!>3 1>2>3 ,花费 20 20 20

数据范围

对于 30 % 30\% 30% 的数据, N < = 100 , M < = 2 , 000 N<=100,M<=2,000 N<=100M<=2,000
对于 100 % 100\% 100% 的数据, 1 < = N , M < = 20 , 000 , 1 < = K < = 200 , 1 < = Q < = 50 , 000 , 1 < = d i < = 10 , 000 1<=N,M<=20,000,1<=K<=200,1<=Q<=50,000, 1<=d_i<=10,000 1<=N,M<=20,0001<=K<=2001<=Q<=50,0001<=di<=10,000

思路

这道题是一道最短路。

我们把所有的纽扣作为起点做 spfa ,然后对于每一个询问,要么可以直接到,要么就可以有一个节点分别到两个点。

然后就把可以完成的请求数量和所有可以完成的请求的花费加起来输出。

代码

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

struct node {
	int to, next, x;
}e[20001];
int n, m, k, q, x, y, z, le[20001], kk, f[201][20001], point[20001], temp, ans1, ans2;
bool in[20001];

void add(int x, int y, int z) {
	e[++kk] = (node){y, le[x], z}; le[x] = kk;
}

void spfa(int s, int num) {
	queue<int>qu;
	memset(f[num], 0x7f, sizeof(f[num]));
	temp = f[num][0];
	f[num][s] = 0;
	qu.push(s);
	in[s] = 1;
	
	while (!qu.empty()) {
		int now = qu.front();
		qu.pop();
		for (int i = le[now]; i; i = e[i].next)
			if (f[num][now] + e[i].x < f[num][e[i].to]) {
				f[num][e[i].to] = f[num][now] + e[i].x;
				if (!in[e[i].to]) {
					in[e[i].to] = 1;
					qu.push(e[i].to);
				}
			}
		in[now] = 0;
	}
}

int main() {
	scanf("%d %d %d %d", &n, &m, &k, &q);//输入
	
	for (int i = 1; i <= m; i++) {
		scanf("%d %d %d", &x, &y, &z);//输入
		add(x, y, z);//连边
	}
	
	for (int i = 1; i <= k; i++) {
		scanf("%d", &x);//读入
		point[x] = i;//记录纽扣
		spfa(x, i);//求出此纽扣到所有点的距离
	}
	
	for (int i = 1; i <= q; i++) {
		scanf("%d %d", &x, &y);//读入
		if (point[x] && f[point[x]][y] != temp) {//能直接到
			ans1++;
			ans2 += f[point[x]][y];
		}
		else if (!point[x]) {//这个点不是纽扣
			int an = temp;
			for (int j = le[x]; j; j = e[j].next)
				if (f[point[e[j].to]][y] != temp)//从一个纽扣可以分别到两个点
					an = min(an, e[j].x + f[point[e[j].to]][y]);//找出最少的哪种方法
			if (an != temp) {//可以到
				ans1++;
				ans2 += an;
			}
		}
	}
	
	printf("%d\n%d", ans1, ans2);//输出
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值