HDU 2363 Cycling

传送门
无向图,给定起点终点,找一条点权极值相差最小的路,在此基础上找最短路。
下面的代码思想是对差值进行二分,因为题目要求差值最小。差值最小是0,差值最大是输入的最大值减最小值。我们取某个差值作为限制条件,拿这个差值来限制最短路算法寻找下一个点的过程。

我想到的一种方法没有AC,而且至今没有想出问题所在,是这样的,把差值确定,但是具体的height上下限不确定,这样去限制SPFA算法,怎么在最短路算法中判断当前进行的路的差值呢?给每个点安排两个值,__min[]__max[],存放从起点走到当前点的最短路的极值,每当找下一个点时,判断下一个点的height是否会破坏中介点存放的历史道路的差值。要说明的是,差值确定的意思是小于等于差值即可,所以某个差值不连通的话,更小的差值就不用试了。另外,考虑这种方法是因为觉得__min[]__max[]的值是随着算法过程而递减和递增的,所以可以中途判断。
然而没有AC,所以肯定有问题。。感觉大概率是不能这么直接用一个差值来限制算法,判断传递过程可能存在逻辑错误,然而不知道测试数据也懒得想反例

(4月13日更新:已经想到反例。且看下图:

在这里插入图片描述

当当前差值是10的时候,错误的方法(一遍SPFA)找不到正确解,那两条虚线边意味着两种情况的无法到达。

找反例的时候先测试了一下错误方法,发现连可行性都判断不正确(在得到最后的l以后又加了一个SPFA循环,但是这样还是错的),也就是说,找到的l都不正确,也就意味着必然有一种限制情况下本来可行,但是让它给判断成不可行了。果然,这种情况被我想出来了。实锤。)

正确的方法是不仅要有差值,还要给定一个下限,这样上限也确定了,范围就确定了,下限就枚举每个点的height(正确答案肯定是以某个点的height为底),这样每次二分就要枚举NSPFA,得到一个同差值不同上下限的最小d[N];多次二分得到全局最优。因为有了上下限,所以最短路算法里直接可以根据height排除下一个点了,逻辑也就比较清晰可证了,没再出什么幺蛾子。
下面放上正确代码,有几处还是要注意的,已注释。


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

const int INF = 1e9;
const int MAXN = 101;
int N, M, T;
struct Edge
{
	int n, w;
};
vector<Edge> ve;
vector<int> v[MAXN];
int height[MAXN];
int d[MAXN];
bool inq[MAXN];
int _min, _max;
int diff;
int ans;

int pre[MAXN];

void init()
{
	_min = INF;
	_max = -1;
	ve.clear();
	for (int i = 1; i <= N; i++) v[i].clear();
	memset(pre, 0, sizeof pre);

	diff = ans = 0;        // 这句防止起点等于终点的极端情况,不然这两个都没有被显示赋初值就被输出去了。但其实想多了
}

void spfa(int s, int low, int high) // 只是定了一个明确的上下限
{
	queue<int> q;
	fill(inq + 1, inq + N + 1, false);
	fill(d + 1, d + N + 1, INF);

	if (height[s] < low || height[s] > high) return;            // 先初始化完d[]再判断,因为调用返回后使用d[]的值

	q.push(s);
	inq[s] = true;
	d[s] = 0;

	for (; !q.empty();)
	{
		int t = q.front();
		q.pop();
		inq[t] = false;
		for (int i = 0; i < v[t].size(); i++)
		{
			int n = ve[v[t][i]].n;
			int w = ve[v[t][i]].w;

			if (height[n] < low || height[n] > high) continue;
			if (d[t] + w < d[n])
			{
				d[n] = d[t] + w;
				pre[n] = t;
				if (!inq[n])
				{
					q.push(n);
					inq[n] = true;
				}
			}
		}
	}
}

void route(int s)
{
	if (s == 1)
	{
		cout << s;
		return;
	}
	route(pre[s]);
	cout << "->" << s;
}

int main()
{
	int a, b, c;
	scanf("%d", &T);
	for (; T--;)
	{
		scanf("%d%d", &N, &M);
		init();
		for (int i = 1; i <= N; i++)
		{
			scanf("%d", &height[i]);
			_min = min(_min, height[i]);
			_max = max(_max, height[i]);
		}
		for (int i = 0; i < M; i++)
		{
			scanf("%d%d%d", &a, &b, &c);
			ve.push_back(Edge{ b,c });
			ve.push_back(Edge{ a,c });
			v[a].push_back(i << 1);
			v[b].push_back(i << 1 | 1);
		}

		int l = 0, r = _max - _min;      // 差值最小和最大,对差值二分
		for (; l <= r;)                  // 这里取等号,l和r刚刚相等时,若上一次循环取了右半边(ans==INF),则还要再来一次枚举才能使diff和ans得到最后正确的值              
		{
			diff = (l + r) >> 1;
			ans = INF;
			for (int i = 1; i <= N; i++)
			{
				spfa(1, height[i], height[i] + diff);
				ans = min(ans, d[N]);
			}
			if (l == r) break;             // 所以这句放的位置非常重要,不放则无限循环,放到底下则上一次循环就退出了(右半边情况还得再来一次)

			if (ans != INF) r = diff;
			else l = diff + 1;
		}
		printf("%d %d\n", diff, ans);
		//route(N);
	}

	return 0;
}


/*
1
6 6
10
0
9
2
2
12
1 2 1
1 3 1
2 5 1
3 4 1
5 6 1
4 5 1

10 4 //正确答案
*/

然后是自己错误的代码。。


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

const int INF = 1e9;
const int MAXN = 101;
int N, M, T;
struct Edge
{
	int n, w;
};
vector<Edge> ve;
vector<int> v[MAXN];
int height[MAXN];
int d[MAXN];
bool inq[MAXN];
int _min, _max;  // 全局最小最大
int limit;
int __min[MAXN];
int __max[MAXN];
int pre[MAXN];

void init()
{
	_min = INF + 1;
	_max = -1;
	ve.clear();
	for (int i = 1; i <= N; i++) v[i].clear();
	memset(pre, 0, sizeof pre);
}

void spfa(int s)
{
	queue<int> q;
	fill(inq + 1, inq + N + 1, false);
	fill(d + 1, d + N + 1, INF);

	q.push(s);
	inq[s] = true;
	d[s] = 0;
	
	fill(__min + 1, __min + N + 1, INF + 1);
	fill(__max + 1, __max + N + 1, -1);
	__min[s] = __max[s] = height[s];

	for (; !q.empty();)
	{
		int t = q.front();
		q.pop();
		inq[t] = false;
		for (int i = 0; i < v[t].size(); i++)
		{
			int n = ve[v[t][i]].n;
			int w = ve[v[t][i]].w;
			if (max(__max[t], height[n]) - min(__min[t], height[n]) <= limit)
			{
				if (d[t] + w < d[n])
				{
					__max[n] = max(__max[t], height[n]);
					__min[n] = min(__min[t], height[n]);
					d[n] = d[t] + w;
					pre[n] = t;
					if (!inq[n])
					{
						q.push(n);
						inq[n] = true;
					}
				}
			}
		}
	}
}

void route(int s)
{
	if (s == 1)
	{
		cout << s;
		return;
	}
	route(pre[s]);
	cout << "->" << s;
}

void spfa2(int s, int low, int high) // 只是定了一个明确的上下限
{
	queue<int> q;
	fill(inq + 1, inq + N + 1, false);
	fill(d + 1, d + N + 1, INF);

	if (height[s] < low || height[s] > high) return;            // 先初始化完d[]再判断,因为调用返回后使用d[]的值

	q.push(s);
	inq[s] = true;
	d[s] = 0;

	for (; !q.empty();)
	{
		int t = q.front();
		q.pop();
		inq[t] = false;
		for (int i = 0; i < v[t].size(); i++)
		{
			int n = ve[v[t][i]].n;
			int w = ve[v[t][i]].w;

			if (height[n] < low || height[n] > high) continue;
			if (d[t] + w < d[n])
			{
				d[n] = d[t] + w;
				pre[n] = t;
				if (!inq[n])
				{
					q.push(n);
					inq[n] = true;
				}
			}
		}
	}
}

int main()
{
	int a, b, c;
	scanf("%d", &T);
	for (; T--;)
	{
		scanf("%d%d", &N, &M);
		init();
		for (int i = 1; i <= N; i++)
		{
			scanf("%d", &height[i]);
			_min = min(_min, height[i]);
			_max = max(_max, height[i]);
		}
		for (int i = 0; i < M; i++)
		{
			scanf("%d%d%d", &a, &b, &c);
			ve.push_back(Edge{ b,c });
			ve.push_back(Edge{ a,c });
			v[a].push_back(i << 1);
			v[b].push_back(i << 1 | 1);
		}

		int l = 0, r = _max - _min, mid;      // 差值最小和最大
		for (; l < r;)                        // 对差值二分
		{
			mid = (l + r) >> 1;
			limit = mid;
			spfa(1);
			if (d[N] != INF) r = mid;
			else l = mid + 1;
		}
		limit = l;
		spfa(1);

		/*int ans = INF;
		for (int i = 1; i <= N; i++)
		{
			spfa2(1, height[i], height[i] + l);         // 测试错误,说明这个方法连可行性都判断错误,也就是说这个方法都找不到正确的l
			ans = min(ans, d[N]);
		}*/
		//printf("%d %d\n", l, ans);

		printf("%d %d\n", l, d[N]);
	}

	return 0;
}

/*
1
6 6
10
0
9
2
2
12
1 2 1
1 3 1
2 5 1
3 4 1
5 6 1
4 5 1

10 4 //正确答案
*/

再然后,在网上又看到一种方法,以(N^2)/2枚举每两个点的height,然后根据高度差排序,然后从小到大依次进行上下限确定的最短路算法,但是我觉得我看到的代码还有点问题,因为没考虑高度差相同但上下限不同的情况,这个时候我们要取多次最短路算法后的最小值。

posted @ 2019-03-09 00:18  CrossingOver  阅读(83)  评论(0编辑  收藏  举报