HDU 7134 题解

HDU 7134 Public Transport System 题解

题目传送门-HDU

题目传送门-vjudge

题目大意

T T T组数据,对于每组数据:有 n n n个点 m m m条边的有向图,求单源最短路。
特殊之处是:每条边除了权值 a a a以外还有一个优惠属性 b b b,只要满足你走过的上一条边的权值严格低于当前边的权值,你就可以获得 b b b的优惠,即优惠后的费用为 a − b a-b ab.

数据范围: 2 ≤ n ≤ 1 0 5 , 1 ≤ m ≤ 2 × 1 0 5 2≤n≤10^5,1≤m≤2×10^5 2n105,1m2×105 1 ≤ T ≤ 1 0 4 1≤T≤10^4 1T104 ∑ n ≤ 6 × 1 0 5 , ∑ m ≤ 1.2 × 1 0 6 \sum n \leq 6 \times 10^5, \sum m \leq 1.2 \times 10^6 n6×105,m1.2×106.

思路分析

朴素的想法

注意到这题就是单源最短路而已,但是由于有优惠的存在,要求我们记得自己上次走过的边,以判断是否能获取优惠。问题是普通的单源最短路都无法做到这一点,它们都只能区分点的不同,而无法区分边。

于是我们转念一想,既然能区分点的不同,那就每当有一条边时,就将它的终点复制一份专门为这条边所用,这样每个点的入度为1,于是你总是知道这个点从哪里来,适不适用优惠。

可是问题又来了:复制点就必须复制从这个点出去的边,这个想法的复杂度怎么样呢?
考虑下图:

1
2
3
4
5
6
7
8

这个图只有8个点,7条边,但是如果要复制点的话会导致图变为下面这个样子:

1
4a
2
4b
3
4c
5
6
7
8

mermaid真的丑
但是总之,点的个数增长不多,但是边却有15条了,换句话说,是 ( m 2 ) 2 (\frac{m}2)^2 (2m)2,特别恐怖。
因此这个做法的最坏复杂度是 O ( m 2 l o g n ) O(m^2log n) O(m2logn),这显然不能满足要求(我TLE了无穷次才明白的道理啊)

优化

显然我们不希望复制如此多的边,那么做什么优化更好呢?

一种想法是,既然一条边只有享受优惠和不享受优惠两种价格,于是我们就只复制一条边,然后大家共用就行,理想的状况如下:

原价
原价
原价
原价
优惠
优惠
优惠
优惠
1
2
4a
3
4b
5
6
7
8

但是,这显然不正确啊!因为不同的边,优惠的临界值不一致啊!

再次优化

不过能够想到这一步,答案离真实结果就不远了。

实际上,只需要先将所有终点相同的边,按照权值 a a a排序,然后由 a a a小的点向 a a a大的点建一个权值为0的边,然后将优惠边建在能享受优惠的最大的 a a a上,将原价边建在最后一个节点上,即可。

如样例1,建图如下(序号后面没有注释的点就是原图中的点,有注释的点即为单独为每条边加的):

3
4
3
7
0
0
0
0
2
1
2
2,a=3
3
3,a=7
3,a=4
3, a=2
4

mermaid渲染好难,我确实不会排版,大家将就看看吧(哭)
从此图中,我们可以看到,2,a=33,a=4的边是一条优惠边,但是这个优惠只有2,a=3以及可能存在的a更小的点(如果有,这些点会有0权边连接2,a=3,而a更大的点会被2,a=3连接,由于是有向边,所以无法回头走)可以享受,而从2直接出发就没有这种优惠(不过从2,a=3出发也可以通过2走一条没有优惠的路)

代码

前面我说的一点儿也不清楚,哎,所以还是直接上代码吧:

#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;

#define re register
typedef long long LL;
const int maxn = 3e5 + 10;
const int maxm = 6e5 + 10;
const int int_inf = 0x3f3f3f3f;
const LL ll_inf = (LL)int_inf << 32 | int_inf;

int head[maxn], nxt[maxm], to[maxm], val[maxm], cnt;
inline void add_edge(int u, int v, int w)
{
	cnt++;
	nxt[cnt] = head[u];
	head[u] = cnt;
	to[cnt] = v;
	val[cnt] = w;
}

int n, m;
typedef pair<LL, int> dis_point;
LL dis[maxn];

priority_queue<dis_point> pq;
bool vis[maxn];
inline void dijkstra()
{
	memset(vis, 0, sizeof(bool) * (n + m + 1));
	dis[1] = 0;
	pq.push(make_pair(0, 1));
	while(!pq.empty())
	{
		re int u = pq.top().second;
		pq.pop();
		if(vis[u]) continue;
		vis[u] = 1;
		for(re int r = head[u]; r; r = nxt[r])
		{
			re LL tw = dis[u] + val[r];
			re int v = to[r];
			if(dis[v] > tw)
			{
				dis[v] = tw;
				pq.push(make_pair(-tw, v)); //默认大根堆,所以负值进入 
			}
		}
	}
}

struct Edge
{
	int u, v, a, b, no;
	bool operator < (const Edge &ot) const
	{
		return v == ot.v ? a > ot.a : v < ot.v; //以入点为第一关键字,费用为第二关键字排序 
	}
	bool operator > (const Edge &ot) const
	{
		return u == ot.u ? a > ot.a : u < ot.u; //以出点为第一关键字,费用为第二关键字排序 
	}
}edge_in[maxn], edge_out[maxn];

int main()
{
	int T;
	scanf("%d", &T);
	while(T--)
	{
		scanf("%d%d", &n, &m);
		memset(head, 0, sizeof(int) * (n + m + 1));
		memset(dis, 0x3f, sizeof(LL) * (n + m + 1));
		cnt = 0;
		//注意:我们构造n+m个点,原来的n个点编号为1~n,新点编号n+1~n+m 
		for(re int i = 1; i <= m; i++)
		{
			edge_in[i].no = i + n; //边的编号是n+1~n+m 
			scanf("%d%d%d%d", &edge_in[i].u, &edge_in[i].v, &edge_in[i].a, &edge_in[i].b);
			edge_out[i] = edge_in[i];
		}
		sort(edge_in + 1, edge_in + m + 1, less<Edge>()); //按照入点排序 
		for(re int i = 1, p = 0; i <= m; i++)
		{
			if(edge_in[i].v != p)
			{
				while(edge_in[i].v != p) p++;
				add_edge(edge_in[i].no, p, 0);
			}
			else
				add_edge(edge_in[i].no, edge_in[i-1].no, 0);
		}
		sort(edge_out + 1, edge_out + m + 1, greater<Edge>()); //按照出点排序 
		for(re int i = 1, j = 1; i <= m; i++)
		{
			add_edge(edge_out[i].u, edge_out[i].no, edge_out[i].a);
			while(j <= m && edge_in[j].v < edge_out[i].u) j++;
			while(j <= m && edge_in[j].v == edge_out[i].u && edge_in[j].a >= edge_out[i].a) j++;
			if(j > m || edge_in[j].v > edge_out[i].u) continue;
			add_edge(edge_in[j].no, edge_out[i].no, edge_out[i].a - edge_out[i].b);
		}
		dijkstra();
		for(re int i = 1; i <= n; i++)
		{
			if(i != 1) printf(" ");
			if(dis[i] == ll_inf) printf("-1");
			else printf("%lld", dis[i]);
		}
		printf("\n");
	}
	return 0;
}

完结撒花!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值