团队内部试题图论/最短路很重的罪

Posted jiangyuechen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了团队内部试题图论/最短路很重的罪相关的知识,希望对你有一定的参考价值。

题目背景

请注意阅读题目中括号里的内容。
(mathbb N):非负整数;(mathbb N*):正整数(没有(0));(mathbb Z):整数。

题目描述

你的面前有(1)条铁轨,这条铁轨在眼前分成(N)条。每条铁轨上都绑着(T_i)个人((T_i in mathbb N* ,0 le i le N))
每个人都犯了一些罪过,所以他们都应该受到火车的碾压。然而火车只有一个,你只能选择一条道路。
绑在铁轨上的每一个人都有一个“罪过程度”(C_{i,j}(C_{i,j} in mathbb Z),即(C_{i,j})可能是负数)。也就是说,给定一组(i,j),能够唯一确定一个(C_{i,j}),你可以把它看作一个坐标。
同时,你发现,有许多铁轨有岔路,而且还连到了另一条铁轨上。岔路上没有任何绑着的人或分支岔道,没有岔道的两端连接同一条铁轨,而且岔路具有单向性,方向由岔路的左偏或右偏决定(具体见样例)。
现在,你想控制电车,使它碾压过的人的“罪过程度”(sum C)最大。具体操作是:从(N)条铁路中任选一条出发,在不倒车的情况下,开过一些铁轨和岔路,直到电车走到一条铁路的尽头(即这之后没有任何一个绑着的人)为止。
你能解决这个问题吗?
示例:

技术图片
其中红圈表示人,上面有其坐标((i,j)),中间的粉色数字表示(C),还有三条岔路。我们可以用一个点组({(x_1,y_1),(x_2,y_2)})表示岔路的起点和终点。((x_{1or2},y_{1or2}))表示的是端点正前方的人的坐标。特别地,若端点前没有人,则(y_{1or2}=0)。比如说上图的蓝色道路可表示为({(3,0),(2,1)})({(2,1),(3,0)})
在图中很明显,(y_1<y_2),则道路从((x_1,y_1))到达((x_2,y_2))。比如说橙色道路({(1,2),(3,3)}),由于(2<3),所以只能从铁轨(1)走到铁轨(3)而不能反着走。蓝色岔道也同理。
特别地,若(y_1=y_2),则将这条岔道视作双向的。 比如说黄色岔道({(3,1),(4,1)}),既能从(3)号铁轨走向(4)号铁轨,也能反着走。
注意:虽然橙色道路跨越了第(2)条铁轨,但是并不连接。也就是说电车走橙色岔道不能在中途切换到第(2)条铁轨上,走第(2)条铁轨也不能走上橙色岔道。
最好的规划是:先走第(4)条铁轨,碾压了((4,1))后走过黄色岔道,登上第(3)条铁轨,再继续碾压((3,2),(3,3),(3,4)),最后走到第(3)条铁轨的终点,能获得最大(sum C_{max}=16)

输入格式

(1)行有两个整数(N,M),分别表示铁轨和岔道的数量。
(2)行有(N)个整数,第(i)个整数表示(T_i)
往下(N)行,第(i)行有(T_{i})个整数,其中第(j)个整数表示(C_{i,j})的值。
再往下(M)行,每行会有四个整数(x_1,y_1,x_2,y_2),表示有一条岔路,从坐标为((x_1,y_1))的人的后边连到了((x_2,y_2))的人的后边。

输出格式

仅输出一个整数(Ans),表示你所能碾压的人的“罪过程度”之总和(sum C)的最大值。

输入样例

4 3
3 2 4 1

4 2 3
2 5
3 3 4 1
8

1 2 3 3
3 0 2 1
3 1 4 1

输出样例

16

提示说明

对于所有数据,(N,M,T<1000),对于任意一个(C)(-2^{31}le Cle 2^{31}-1)
注意时间限制。

题解代码(解释在代码里)

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
/* 
*  @brief 大体思路:
*  	@param 1.由于二维坐标直接连边比较困难(当然不排除许多巨佬直接二维连边爆切此题),所以可以转化成一维。
*		以下注释称转化成一维的坐标为“真实数字”。
*  	@param 2.以含有人最多的铁轨为“Tmax”,将人数不足Tmax的铁轨后面补上一些C=0的人。形成优美的矩形结构(具体见图)。
*  	@param 3.计算前缀和(基于真实数字),方便之后使用。
*  	@param 4.通过转化后的点来连边。为了计算两个相邻岔道的端点之间C的总值(即边权),使用前缀和。
*		一条铁轨的第一个岔道端点向起点连边,最后一个岔道端点向终点连边。
*  	@param 5.使用SPFA跑最长路。
*  @brief 时间复杂度(以下的N为总人数(但题目中表示铁轨数,可以理解为题目中的T*N),M为岔道数):
*   @param 1.计算前缀和:O(N);
*   @param 2.连边(最坏情况,假设每个人的正后方都有铁轨):O(N+M);
*	@param 3.跑最长路:O(K(N+M))(数据均随机,K约等于2)。
*/
/////////////////////////
using namespace std;
const int MAXEDGE = 5000001;															  //最大边数。如果每个人之间都有岔路,那么它的最大值就是sum(T[i]).
const int MAXPOINT = 5000001;															  //最大点数(即最大人数,显然是sum(T[i]))
const int MAXPATH = 1001;																  //最大岔道数
const int _ST_TO_FN = 0 /*起点到终点*/, _FN_TO_ST = 1 /*终点到起点*/, _BOTH = 2 /*双向*/; //判定岔道的类别
typedef pair<int, int> point;															  //点,记录坐标
struct path
{ //岔道
	int st /*起点*/, fn /*终点*/;
};
//↓点和真实数字互相转化的工具函数(声明)
int getrange(int, int);		  //获得前缀和
int getrange(int);			  //获得一个点到这一行结尾的前缀和
int __realNum(int, int);	  //!(关键)一个点对应的真实数字
point __Point(int);			  //将真实数字转换为点
int __dir(path &);			  //判断岔道的方向
void add_edge(int, int, int); //图加边
int __line_end(int);		  //返回第n条铁轨的起点对应的真实数字
int __line_head(int);		  //返回第n条铁轨的终点对应的真实数字
//↑点和真实数字互相转化的工具函数(声明)
//↓链式前向星内容
int head[MAXPOINT], tot;
int ver[MAXEDGE], edge[MAXEDGE], nxt[MAXEDGE];
void add_edge(int st, int fn, int w)
{
	ver[++tot] = fn;
	edge[tot] = w;
	nxt[tot] = head[st];
	head[st] = tot;
}
//↑链式前向星内容
//↓题目所有输入的数据保存在这里
int N, M, T[MAXPOINT], C[MAXPOINT]; //N:铁轨数量,M:岔道数量,T[i]:第i条铁轨上绑着的人数,C[i]:真实数字为i的人的罪过程度
bool isthere[MAXPOINT];				//真实数字为i的人的后面是否有一条岔道
path p[MAXPATH];					//所有的岔道
int Tmax = -1;						//最大的T
int qzh[MAXPOINT];					//前缀和
//↑题目所有输入的数据保存在这里
//↓点和真实数字互相转化的工具函数(定义)
inline int getrange(int st, int fn)
{
	return qzh[fn] - qzh[st];
}
inline int getrange(int st)
{
	return qzh[st - (st % Tmax) + Tmax - 1] - qzh[st];
}
inline int __realNum(int x, int y)
{
	return (x - 1) * Tmax + y;
}
inline point __Point(int n)
{
	return make_pair(n / Tmax + 1, n % Tmax);
}
inline int __dir(path &p)
{
	point _st = __Point(p.st), _fn = __Point(p.fn);
	if (_st.second < _fn.second)
		return _ST_TO_FN;
	else if (_st.second > _fn.second)
		return _FN_TO_ST;
	else
		return _BOTH;
}
inline int __line_head(int n)
{
	return (n - 1) * Tmax;
}
inline int __line_end(int n)
{
	return __line_head(n) + Tmax - 1;
}
//↑点和真实数字互相转化的工具函数(定义)
//↓SPFA
bool vis[MAXPOINT];
int dist[MAXPOINT];
queue<int> q;
int spfa(int start, int finish)
{

	memset(dist, 0x3f, sizeof(dist));
	memset(vis, 0, sizeof(vis));
	dist[start] = 0;
	vis[start] = 1;
	q.push(start);
	while (!q.empty())
	{
		int x = q.front();
		q.pop();
		vis[x] = 0;
		for (int i = head[x]; i; i = nxt[i])
		{
			int y = ver[i], z = edge[i];
			if (dist[y] > dist[x] + z)
			{
				dist[y] = dist[x] + z;
				if (!vis[y])
					q.push(y), vis[y] = 1;
			}
		}
	}
	return -dist[finish];
}
//↑SPFA
//设起点编号为-1,终点编号为-2.
int main()
{
	ios::sync_with_stdio(false);
	cin >> N >> M;
	for (int i = 1; i <= N; i++)
	{
		cin >> T[i];
		Tmax = max(Tmax, T[i] + 1);
	}
	for (int i = 1; i <= N; i++)
	{
		for (int j = 1; j <= T[i]; j++)
		{
			int now = __realNum(i, j);
			cin >> C[now];
		}
	}
	for (int i = 1; i <= N; i++)
	{
		for (int j = 0; j < Tmax; j++)
		{
			int now = __realNum(i, j);
			qzh[now] = qzh[now - 1] + C[now];
		}
	}
	for (int i = 1, x1, x2, y1, y2; i <= M; i++)
	{
		cin >> x1 >> y1 >> x2 >> y2;
		p[i].st = __realNum(x1, y1);
		p[i].fn = __realNum(x2, y2);
		isthere[p[i].st] = isthere[p[i].fn] = true;
		if (p[i].st > p[i].fn)
			swap(p[i].st, p[i].fn); //不妨设岔道的起点在上方,终点在下方。
		switch (__dir(p[i]))
		{
		case _ST_TO_FN:
			add_edge(p[i].st, p[i].fn, 0);
			break;
		case _FN_TO_ST:
			add_edge(p[i].fn, p[i].st, 0);
			break;
		default: //_BOTH
			add_edge(p[i].st, p[i].fn, 0);
			add_edge(p[i].fn, p[i].st, 0);
			break;
		}
	}
	//处理铁轨
	for (int i = 1; i <= N; i++)
	{
		int pre = __line_head(i);
		bool isfirst = true;
		for (int now = __line_head(i); now <= __line_end(i); now++)
		{
			if (isthere[now])
			{
				//cout<<"find in "<<now<<endl;
				if (isfirst)
				{ //第一条边,要向起点连
					add_edge(-1, now, -getrange(__line_head(i), now));
					isfirst = false;
				}
				else
				{
					add_edge(pre, now, -getrange(pre, now));
				}
				pre = now; //前驱转换
			}
		}
		add_edge(pre, -2, -getrange(pre)); //向终点连一条边
	}
	//spfa跑最长路,完事!
	cout << spfa(-1, -2) << endl;//这里估计崩了,忘了负数作为下标访问数组会造成溢出(虽然输出都对)
	//system("pause");
	return 0;
}






















以上是关于团队内部试题图论/最短路很重的罪的主要内容,如果未能解决你的问题,请参考以下文章

一个很重的代码坏味:多行代码糅合在一行

软件工程代码质量综合指南:最佳实践和工具

图论训练之三

图论算法之最短路径

洛谷图论入门题--基本题必做 图-最短路径-1.信使(msner)

图论算法最短路算法:Floyd算法!