【差分约束】P5590 赛车游戏|省选-
本文涉及知识点
【数学 线性代数】差分约束
P5590 赛车游戏
题目描述
R 君和小伙伴打算一起玩赛车。但他们被老司机 mocania 骗去了秋名山。
秋名山上有 n n n 个点和 m m m 条边,R 君和他的小伙伴要从点 1 1 1 出发开往点 n n n,每条边都有一个初始的方向。老司机 mocania 拿到了秋名山的地图但却不知道每条路有多长。显然,为了赛车游戏的公平,每条 1 1 1 到 n n n 的路径应当是等长的。mocania 想,我就随便给边标上一个 1...9 1...9 1...9 的长度,反正傻傻的 R 君也看不出来。
可 mocania 的数学不大好,不知道怎么给边标长度,只能跑来请教你这个 OI 高手了。
输入格式
第一行两个整数 n , m n,m n,m。
接下来 m m m 行,每行两个整数 u , v u,v u,v,表示一条从 u u u 到 v v v 的有向边。
输出格式
如果无解或者不存在 1 1 1 到 n n n 的路径直接输出一个 − 1 -1 −1。
如果有解第一行输出两个数 n , m n,m n,m,和输入文件中给出的相同。
接下来 m m m 行,每行三个整数 u , v , w u,v,w u,v,w,表示把从 u u u 到 v v v 的路径的长度设置为 w w w,其中 w w w 是一个 1 ∼ 9 1\sim 9 1∼9 的整数。要求所有边的出现顺序和题目中给出的相同。
输入输出样例 #1
输入 #1
10 10
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9
9 10
1 10
输出 #1
10 10
1 2 1
2 3 1
3 4 1
4 5 1
5 6 1
6 7 1
7 8 1
8 9 1
9 10 1
1 10 9
说明/提示
数据范围
本题启用 Special Judge 和 Subtask。
Subtask #1(
30
30
30 分):
n
≤
10
n \leq 10
n≤10,
m
≤
20
m \leq 20
m≤20;
Subtask #2(
30
30
30 分):
n
≤
100
n \leq 100
n≤100,
m
≤
200
m \leq 200
m≤200;
Subtask #3(
40
40
40 分):
n
≤
1000
n \leq 1000
n≤1000,
m
≤
2000
m \leq 2000
m≤2000。
保证数据中不会出现重边,自环。
P5590 赛车游戏 差分约束
一,以1为起点BFS,如果无法到达N点,返回-1。
二,所有边反转,从N开始BFS。两轮BFS都到达的点才处理,有一轮没达到的点及相连的边删除。
三,假定各边的权重为1,求各节点到1的最长路leves[i]。如果有环(正环),返回-1。
四,按leves[i] 从大到小枚举各节点, 枚举i的后续节点j。m1 = min(leves[j]),m2=max(leves[j]),有leves[i] < leves[j],故leves[i] < m1恒成立。确保leves[i] < m1的前提上, 增加leves[i] 及其前置节点,使得leves[j]-leves[i] <=9.
第四步完成后,leves[j] - leves[i] 就是i到j 的权。如果leves[i] 有多个合法值,都要枚举。
综合3到5就是差分约束。leves[i] < leves[j] ,即levevs[i] -leves[j] <=-1,leves[j]-leves[i] <=9。由于levevs[i] -leves[j]<=-1,地图有环,差分系统的有向图也一定有环。
由于边权最大9,边数1000。故1e4就是极大值。增加0到各点权位1e4的边。以0为起点,求最小路径。
总结
删除非起点终点路径上的点和边后。起点到任意点的任意路径路程都相等,否则和起点到终点任意路径相等矛盾。vis[i]记录起点到i的最短路,也就是起点i到任意路径的路程。
u有边指向v,则:uv的边权是dis[v]-dis[u],可以是1到9的任意值。
本题是差分约束的充分条件,故差分约束,无解,则本题无解。
下面用数学归纳法来证明差分的系统的解一定符合所有路径都是最短路。
经过0条边的路径,只有一条,一定符合本题。
如果经过i条边的路径都符合,则经过i+1条边的路径也符合。令第i+1条边是u
→
\rightarrow
→v,则此路径的路程是:dis[u]+(dis[v]-disv[u])= div[v]就是最短路。
注意:忽略的边任意权值。
代码
核心代码
#include <iostream>
#include <sstream>
#include <vector>
#include<map>
#include<unordered_map>
#include<set>
#include<unordered_set>
#include<string>
#include<algorithm>
#include<functional>
#include<queue>
#include <stack>
#include<iomanip>
#include<numeric>
#include <math.h>
#include <climits>
#include<assert.h>
#include<cstring>
#include<list>
#include <bitset>
using namespace std;
template<class T1, class T2>
std::istream& operator >> (std::istream& in, pair<T1, T2>& pr) {
in >> pr.first >> pr.second;
return in;
}
template<class T1, class T2, class T3 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3>& t) {
in >> get<0>(t) >> get<1>(t) >> get<2>(t);
return in;
}
template<class T1, class T2, class T3, class T4 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3, T4>& t) {
in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t);
return in;
}
template<class T = int>
vector<T> Read() {
int n;
scanf("%d", &n);
vector<T> ret(n);
for (int i = 0; i < n; i++) {
cin >> ret[i];
}
return ret;
}
template<class T = int>
vector<T> Read(int n) {
vector<T> ret(n);
for (int i = 0; i < n; i++) {
cin >> ret[i];
}
return ret;
}
template<int N = 1'000'000>
class COutBuff
{
public:
COutBuff() {
m_p = puffer;
}
template<class T>
void write(T x) {
int num[28], sp = 0;
if (x < 0)
*m_p++ = '-', x = -x;
if (!x)
*m_p++ = 48;
while (x)
num[++sp] = x % 10, x /= 10;
while (sp)
*m_p++ = num[sp--] + 48;
AuotToFile();
}
void writestr(const char* sz) {
strcpy(m_p, sz);
m_p += strlen(sz);
AuotToFile();
}
inline void write(char ch)
{
*m_p++ = ch;
AuotToFile();
}
inline void ToFile() {
fwrite(puffer, 1, m_p - puffer, stdout);
m_p = puffer;
}
~COutBuff() {
ToFile();
}
private:
inline void AuotToFile() {
if (m_p - puffer > N - 100) {
ToFile();
}
}
char puffer[N], * m_p;
};
template<int N = 1'000'000>
class CInBuff
{
public:
inline CInBuff() {}
inline CInBuff<N>& operator>>(char& ch) {
FileToBuf();
ch = *S++;
return *this;
}
inline CInBuff<N>& operator>>(int& val) {
FileToBuf();
int x(0), f(0);
while (!isdigit(*S))
f |= (*S++ == '-');
while (isdigit(*S))
x = (x << 1) + (x << 3) + (*S++ ^ 48);
val = f ? -x : x; S++;//忽略空格换行
return *this;
}
inline CInBuff& operator>>(long long& val) {
FileToBuf();
long long x(0); int f(0);
while (!isdigit(*S))
f |= (*S++ == '-');
while (isdigit(*S))
x = (x << 1) + (x << 3) + (*S++ ^ 48);
val = f ? -x : x; S++;//忽略空格换行
return *this;
}
template<class T1, class T2>
inline CInBuff& operator>>(pair<T1, T2>& val) {
*this >> val.first >> val.second;
return *this;
}
template<class T1, class T2, class T3>
inline CInBuff& operator>>(tuple<T1, T2, T3>& val) {
*this >> get<0>(val) >> get<1>(val) >> get<2>(val);
return *this;
}
template<class T1, class T2, class T3, class T4>
inline CInBuff& operator>>(tuple<T1, T2, T3, T4>& val) {
*this >> get<0>(val) >> get<1>(val) >> get<2>(val) >> get<3>(val);
return *this;
}
template<class T = int>
inline CInBuff& operator>>(vector<T>& val) {
int n;
*this >> n;
val.resize(n);
for (int i = 0; i < n; i++) {
*this >> val[i];
}
return *this;
}
template<class T = int>
vector<T> Read(int n) {
vector<T> ret(n);
for (int i = 0; i < n; i++) {
*this >> ret[i];
}
return ret;
}
template<class T = int>
vector<T> Read() {
vector<T> ret;
*this >> ret;
return ret;
}
private:
inline void FileToBuf() {
const int canRead = m_iWritePos - (S - buffer);
if (canRead >= 100) { return; }
if (m_bFinish) { return; }
for (int i = 0; i < canRead; i++)
{
buffer[i] = S[i];//memcpy出错
}
m_iWritePos = canRead;
buffer[m_iWritePos] = 0;
S = buffer;
int readCnt = fread(buffer + m_iWritePos, 1, N - m_iWritePos, stdin);
if (readCnt <= 0) { m_bFinish = true; return; }
m_iWritePos += readCnt;
buffer[m_iWritePos] = 0;
S = buffer;
}
int m_iWritePos = 0; bool m_bFinish = false;
char buffer[N + 10], * S = buffer;
};
template<class T = int, T iDef = INT_MAX / 2>
class CDisNegativeRing //贝尔曼-福特算法
{
public:
bool Dis(int N, vector<tuple<int, int, int>> edgeFromToW, int start) {
vector<T> pre(N, iDef);
pre[start] = 0;
for (int t = 0; t < N; t++) {
auto cur = pre;
for (const auto& [u, v, w] : edgeFromToW) {
cur[v] = min(cur[v], pre[u] + w);
}
if (t + 1 == N) {
for (int i = 0; i < N; i++) {
if (pre[i] != cur[i]) { return false; }
}
}
pre.swap(cur);
}
m_vDis = pre;
return true;
}
vector<T> m_vDis;
};
class CNeiBo
{
public:
static vector<vector<int>> Two(int n, const vector<pair<int, int>>& edges, bool bDirect, int iBase = 0)
{
vector<vector<int>> vNeiBo(n);
for (const auto& [i1, i2] : edges)
{
vNeiBo[i1 - iBase].emplace_back(i2 - iBase);
if (!bDirect)
{
vNeiBo[i2 - iBase].emplace_back(i1 - iBase);
}
}
return vNeiBo;
}
static vector<vector<int>> Two(int n, const vector<vector<int>>& edges, bool bDirect, int iBase = 0)
{
vector<vector<int>> vNeiBo(n);
for (const auto& v : edges)
{
vNeiBo[v[0] - iBase].emplace_back(v[1] - iBase);
if (!bDirect)
{
vNeiBo[v[1] - iBase].emplace_back(v[0] - iBase);
}
}
return vNeiBo;
}
static vector<vector<std::pair<int, int>>> Three(int n, vector<vector<int>>& edges, bool bDirect, int iBase = 0)
{
vector<vector<std::pair<int, int>>> vNeiBo(n);
for (const auto& v : edges)
{
vNeiBo[v[0] - iBase].emplace_back(v[1] - iBase, v[2]);
if (!bDirect)
{
vNeiBo[v[1] - iBase].emplace_back(v[0] - iBase, v[2]);
}
}
return vNeiBo;
}
static vector<vector<int>> Mat(vector<vector<int>>& neiBoMat)
{
vector<vector<int>> neiBo(neiBoMat.size());
for (int i = 0; i < neiBoMat.size(); i++)
{
for (int j = i + 1; j < neiBoMat.size(); j++)
{
if (neiBoMat[i][j])
{
neiBo[i].emplace_back(j);
neiBo[j].emplace_back(i);
}
}
}
return neiBo;
}
};
class CBFSDis
{
public:
CBFSDis(vector<vector<int>>& vNeiB, vector<int> start)
{
m_vDis.assign(vNeiB.size(), m_iNotMayDis);
queue<int> que;
for (const auto& n : start)
{
m_vDis[n] = 0;
que.emplace(n);
}
while (que.size())
{
const int cur = que.front();
que.pop();
for (const auto next : vNeiB[cur])
{
if (m_iNotMayDis != m_vDis[next])
{
continue;
}
m_vDis[next] = m_vDis[cur] + 1;
que.emplace(next);
}
}
}
public:
const int m_iNotMayDis = 1e9;
vector<int> m_vDis;
};
class Solution {
public:
vector<int> Ans(int N, const vector<pair<int, int>>& edge) {
auto neiBo = CNeiBo::Two(N, edge, true, 1);
vector<vector<int>> neiBoBack(N);
for (int i = 0; i < N; i++) {
for (const auto& j : neiBo[i]) {
neiBoBack[j].emplace_back(i);
}
}
CBFSDis dis1(neiBo, { 0 });
if (dis1.m_iNotMayDis == dis1.m_vDis.back()) { return {}; }
CBFSDis dis2(neiBoBack, { N - 1 });
vector<tuple<int, int, int>> edge2;
auto Not = [&](int i, int j) {
if ((dis1.m_vDis[i] > N) || (dis1.m_vDis[j] > N)) { return true; }
if ((dis2.m_vDis[i] > N) || (dis2.m_vDis[j] > N)) { return true; }
return false;
};
for (auto [i, j] : edge) {
i--, j--;
if (Not(i, j)) { continue; }
//eves[i] < leves[j] ,即levevs[i] -leves[j] <=-1,leves[j]-leves[i] <=9。由于levevs[i] -leves[j]<=-1,
edge2.emplace_back(j, i, -1);
edge2.emplace_back(i, j, 9);
}
CDisNegativeRing dis;
if (!dis.Dis(N, edge2, 0)) { return {}; }
vector<int> ans;
for (auto [i, j] : edge) {
i--, j--;
if (Not(i, j)) { ans.emplace_back(9); continue; }
ans.emplace_back(dis.m_vDis[j] - dis.m_vDis[i]);
}
return ans;
}
};
int main() {
#ifdef _DEBUG
freopen("a.in", "r", stdin);
#endif // DEBUG
ios::sync_with_stdio(0);
int n, m ;
cin >> n >> m ;
auto ope = Read<pair< int,int>>(m);
#ifdef _DEBUG
printf("n=%d", n);
Out(ope, "edge=");
//Out(ope, ",ope=");
//Out(edge2, ",edge2=");
/*Out(que, "que=");*/
#endif // DEBUG
auto res = Solution().Ans(n,ope);
if (res.empty()) {
cout << -1;
}
else {
cout << n << " " << m << "\n";
for (int i = 0; i < m; i++) {
cout << ope[i].first << " " << ope[i].second << " " << res[i] << "\n";
}
}
return 0;
}
单元测试
int n;
vector<pair<int, int>> edge;
TEST_METHOD(TestMethod1)
{
n = 3, edge = { {1,2} };
auto res = Solution().Ans(n, edge);
AssertV({}, res);
}
TEST_METHOD(TestMethod2)
{
n = 10,edge = { {1,2},{2,3},{3,4},{4,5},{5,6},{6,7},{7,8},{8,9},{9,10},{1,10} };
auto res = Solution().Ans(n, edge);
vector<int >act(edge.size(), 1);
act.back() = 9;
AssertV(act, res);
}
TEST_METHOD(TestMethod3)
{
n = 3, edge = { {1,3},{1,2} };
auto res = Solution().Ans(n, edge);
AssertV({9,9}, res);
}
扩展阅读
我想对大家说的话 |
---|
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。 |
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作 |
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注 |
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
失败+反思=成功 成功+反思=成功 |
视频课程
先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。