Trips and Universities题解


Trips and Universities

前置知识

图的基础知识(只看前一部分就行)

分析题目

有向图,边有边权。不考虑学院的话其实就是一道普通的最短路问题。

那么,现在考虑进学院。我们注意到,题目中学院的条件很奇怪:

必须上一次在 $p_i$ 城市的学院学习才能在该学院学习。每个学院只能学习一次。

其实,对于处理这种较为特殊的、看起来要使用最短路解决的问题,我们有一种建图技巧:分层图。

简单来说,就是将问题划分为若干个阶段(通常很少),对于每一个阶段,我们都建一层完整的图,层与层之间根据题目要求用合适的边连接。

比方说这道题,我们将每修完一所学院划为一个阶段。学院 \(i\) 相当于在当前层于下一层的对应节点之间连接了一条边权为 \(c_i - g_i\) 的边。在这个图上跑最短路即可。

细节实现参见代码。

代码

#define INF 0x3f3f3f3f3f3f3f3f

#include 
#include 
#include 
#include 
#include 

using namespace std;

inline int read(){
	int t = 0,flag = 1;
	register char c = getchar();
	while (c < 48 || c > 57) {if (c == '-') flag = -1;c = getchar();}
	while (c >= 48 && c <= 57) t = (t << 1) + (t << 3) + (c ^ 48),c = getchar();
	return flag * t;
}//快读,可忽略 

inline long long min(long long a,long long b) {return a < b ? a : b;}

struct edge{
	int u,v,w,next;
}e[40601];

queue  q;

int n,m,k,u,v,w,f,c,g,p,tot,etot,head[20001],from[60],to[50];//(from[i],to[i])代表i学院在图中的边从第from[i]层连到to[i]层 
long long ans[200001],dis[20001];
bool inque[20001];

void addedge(int u,int v,int w){
	e[++etot].u = u,e[etot].v = v,e[etot].w = w,e[etot].next = head[u],head[u] = etot;
}//建图 

void SPFA(){
	memset(inque,0,sizeof(inque)),memset(dis,63,sizeof(dis)),q.push(1),inque[1] = 1,dis[1] = 0;
	while (!q.empty()){
		int now = q.front();
		q.pop(),inque[now] = 0;
		for (int i = head[now];i;i = e[i].next){
			if (dis[e[i].v] > dis[now] + e[i].w){
				dis[e[i].v] = dis[now] + e[i].w;
				if (!inque[e[i].v]) q.push(e[i].v),inque[e[i].v] = 1;
			}
		}
	}
}//最短路:SPFA。写Bellman-ford应该也行

int main(){
	from[0] = -1,n = read(),m = read(),k = read();//将from[0]设为-1是为了后面方便 
	for (int i = 1;i <= m;i++){
		u = read(),v = read(),w = read();
		for (int j = 0;j <= k;j++){
			addedge(u + n * j,v + n * j,w);//先建好每一层图,层与层之间先不连通,可以举例理解 
		}
	}
	for (int i = 1;i <= k;i++){
		f = read(),c = read(),g = read(),p = read();
		from[f] = to[p],to[f] = ++tot;//更新层数 
		addedge(f + n * from[f],f + n * to[f],c - g);//连边,可以自己举几个例子理解 
	}	
	SPFA();
	for (int i = 1;i <= n;i++){
		ans[i] = INF;//因为要取最小值,先将答案初始化为无穷 
		for (int j = 0;j <= k;j++) ans[i] = min(ans[i],dis[i + n * j]);//由于到哪一层停止都可以,所以要每一层取最小值 
		if (ans[i] == INF) puts("ERROR");//ans[i] == INF 说明ans[i]没有更新,即无法到达i 
		else printf("%lld\n",ans[i]);
	}
	return 0;
}