【Gym - 101806X】Xtreme NP-hard Problem【2018 KAIST RUN Spring Contest】【NP-hard】【大搜索】

Description:

Caution! This problem turned out to be NP-hard. But since there were no rules against writing an NP-hard problem, we decided to leave this problem here.

There is a bidirectional graph consisting of n vertices and m edges. The vertices and edges are numbered from 1 to n and 1 to m respectively, and the weight of edge iis wi. (1 ≤ i ≤ m) Given a natural number k, find the length of the shortest simple path that starts from vertex 1 and ends at vertex n, and consists of k edges. A simple path is a path that does not visit same vertex twice, and length of a path is the sum of weight of edges that consists the path.

 

Input:

In the first line, three space-separated integers n,  m,  k are given. (2 ≤ n < 106, 1 ≤ m,  k < 106, min(n,  m,  k) ≤ 5)

In the next m lines, three space-separated integers xi,  yi,  wi are given. They denote that edge i is connecting vertex xi and vertex yi, and has weight wi. (1 ≤ xi,  yi ≤ n, 1 ≤ wi ≤ 108)

No loops or multiple edges are given.

 

Output:

Print the length of the shortest simple path that starts from vertex 1 and ends at vertex n, and consists of k edges. If there is no such path, print -1.

 

题意:

        给一个无向图,没有自环和多重边,让你找出一条从1-n的包含K条边的路径,要求这条路径最短,并且不重复经过同一个点。

        然后输出这条路径的最小值,如果没有,就输出 -1 。

思路:

        因为 min(n,m,k) <= 5,因此不难发现,k一定小于等于5,由此可以考虑各种暴力做法。

        一开始看到这题,以为是最短路水题,给每个点打个标记,标记到达这个点经过了多少条边,然后直接跑 dijstra,跑到了第 27 个点,然后wa了......

        后来想想不对,中间某一个点可能会被一条最短路更新,但这条路不是最优解,那么这个算法就错了。

        在这之后,队友觉得K比较小,貌似可以直接搜索,就打了个dfs上去,然后无情T在了第20个点......

        然后我又想了想,换了个算法,改成分层图去跑 dijstra ,然后给每个点记录一个前驱,当你要更新一个新的点的时候,用前驱来验证一下你接下来要跑的那个点是否在你之前的路径上,避免重复跑到同一个点,用这种算法去跑的话,能跑到第31个点...

        在这之后,比赛就结束了......在这场比赛中,我全场像个zz一样怼一个没人A的题,这种zz行为简直可以登榜我的zz记录本......

        赛后再仔细想了想,还是一开始dij那个问题,就算你进行了分层,但你一旦更新了一个点的最短路,而且这个最短距离所在的层次正好是正确答案所在的层次,那么跑正确答案的时候就跑不动了,因此虽然分层可以多过一些点,但是算法依然是错误的。

        之后就去查题解了,然后研究题解C++11上的 e xin 代码。算是明白了思路。

        本题的思路就是,如果 k >= n || k > m || k > 5,直接输出-1,因此本题k的最大值就是5。

        然后我们来思考当k为5的时候我们应该如何求解。

        正确的求解姿势是,对着两个端点各自枚举边数为2的点。

【这里用的方法有点类似队友那个直接dfs了,此处其实类似于双向bfs,大大减少计算量】

        对于每个点都求出到 1号点 和 n号点 到该点的最短距离,次短距离,次次短距离,然后枚举所有边,并且记录每个点最短距离、次短距离、次次短距离的前驱,验证这条路径上的五个点是否互不相同,然后更新 ans 即可。       

        这里为什么要记录3个最短距离呢,原因是满足最短距离的时候可能并不合法,即 1-i 的最短路径上经过了 j-n 上的点,而不合法的时候就需要再去验证次短距离了。

        由于 j-n 上最多只有3个点,所以记录3个距离即可,一开始我记录了四个最短距离,A了之后,改成三个发现也可以过。

        然后就是一个大型搜索现场,对着所有边进行枚举,对着每个点的三个距离进行 3*3 的组合验证,然后再一一对ans进行更新。

        所以对于K = 5的情况我们就解决了,K < 5的情况,就直接在n点后面补虚点,然后将K加到5即可。

反思:

        本题打了一天......一共wa了34发......各种算法换来换去,现在就感觉自己是个zz。

        本题在题干中就已经提醒过我们这是一个 NP-hard 问题,虽然对于P与NP问题了解的很浅,但是至少也应该知道,P问题的时间复杂度是由多项式表达的,例如 O(n),O(nlogn)等。但NP问题的时间复杂度目前没有多项式表达的算法可以实现,既然如此,那尝试dijstra、分层图dijstra,怕不是石乐志,妄图想成为一名计算机理论学家吗?!

        自己简直就像是一个弟弟,zz整场,没被队友干掉,我真是感激涕零......

        而对于NP问题,大部分的解决方法是直接进行搜索,时间复杂度是指数级的,当然并不是所有的NP问题都是用搜索进行解决,比如背包问题,就是用动态规划进行解决,但是动态规划算法的复杂度依然是指数级别的。

        因此正常的思路应该就是看到NP,然后想到,噢!搜索问题!那该怎么搜呢?我们先看看K的范围好了,噢!K最大是5,那对K == 5该如何解决呢?

        直接搜五步?不对,首先时间复杂度就不行,那就双向bfs搜?好像时间复杂度可行。

        那就从起点跑两个点,终点跑两个点,枚举所有边好了。记得验证这条路径上不能有重复点。

        还是有问题,如果最短路不符合题意,而次短路确是答案该怎么办?那就给每个点记录是三个距离好了!

        然后3*3暴力匹配,这样应该就行了!嗯,那既然K == 5可以解决了,那对于K < 5的时候,直接补虚点好了!

        ......

总结:

        题目虽然做完了,但是还是需要总结一下自己学到了什么。

        首先是NP问题的概念,这次应该印象很深刻了......没想到题意这么简单的一个问题,解决起来却如此麻烦......

        其次是搜索的问题,一定要注意观察数据范围,当K很小的时候,可以从起点跑几个点,再从终点跑几个点,然后枚举边或者枚举点,这里需要注意。

        再然后就是虚点的问题,通过加虚点这个方式,可以将大量的情况都归纳在最大的那一种情况之中,这个技巧也需要掌握!

代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#define rep(i,a,b) for(int i = a;i <= b;i++)
using namespace std;
const int N = 1e6+1000;
const int M = 2*1e6+1000;
typedef long long ll;
const int inf = 1e9;

struct Edge{
	int to,next;
	int w;
}e[M];
int head[N],tot,pre1[N][5],pre2[N][5];
int n,m,k,tl;
int dis1[N][5],dis2[N][5],ans;
struct Line{
	int u,v,w;
}line[M];

void init()
{
	tl = 0;
	tot = 1;
	rep(i,1,n) head[i] = 0;
}

void add(int x,int y,ll w)
{
	e[++tot].to = y, e[tot].next = head[x], e[tot].w = w, head[x] = tot;
}

int judge(int a,int b,int id1,int id2)
{
	if(dis1[a][id1] == inf || dis2[b][id2] == inf) return 0;
	int a1 = pre1[a][id1], b1 = pre2[b][id2];
	if(a == 1 || a == n || a == b || a == b1 || a == a1 || a1 == -1 || b1 == -1 || a == -1 || b == -1) return 0;
	if(a1 == 1 || a1 == n || a1 == b || a1 == b1) return 0;
	if(b == 1 || b == n || b == b1) return 0;
	if(b1 == 1 || b1 == n ) return 0;
	return 1;
}

void cal(int a, int b, int id1, int id2,int w)
{
	if(judge(a,b,id1,id2))
		ans = min(ans,dis1[a][id1]+dis2[b][id2]+w);
}

void bfs()
{
	rep(i,1,n) 
		rep(j,0,3)
			dis1[i][j] = inf,dis2[i][j] = inf;
	rep(i,1,n) 
		rep(j,0,3)
			pre1[i][j] = -1, pre2[i][j] = -1;
	for(int i = head[1]; i ;i = e[i].next)
	{
		int x = e[i].to;
		int v1 = e[i].w;
		if(x == 1 || x == n) continue;
		for(int j = head[x]; j ; j = e[j].next)
		{
			int y = e[j].to;
			int v2 = e[j].w;
			if(y == 1 || y == n || y == x) continue;
			if(dis1[y][0] > v1+v2)
			{
				dis1[y][2] = dis1[y][1], pre1[y][2] = pre1[y][1];
				dis1[y][1] = dis1[y][0], pre1[y][1] = pre1[y][0];
				dis1[y][0] = v1+v2, pre1[y][0] = x;
			}
			else if(dis1[y][1] > v1+v2)
			{
				dis1[y][2] = dis1[y][1], pre1[y][2] = pre1[y][1];
				dis1[y][1] = v1+v2, pre1[y][1] = x;	
			}	
			else if(dis1[y][2] > v1+v2)
			{
				dis1[y][2] = v1+v2, pre1[y][2] = x;
			}
		}
	}	
	for(int i = head[n]; i ;i = e[i].next)
	{
		int x = e[i].to;
		int v1 = e[i].w;
		if(x == 1 || x == n) continue;
		for(int j = head[x]; j ; j = e[j].next)
		{
			int y = e[j].to;
			int v2 = e[j].w;
			if(y == n || y == 1 || y == x) continue;
			if(dis2[y][0] > v1+v2)
			{
				dis2[y][2] = dis2[y][1], pre2[y][2] = pre2[y][1];
				dis2[y][1] = dis2[y][0], pre2[y][1] = pre2[y][0];
				dis2[y][0] = v1+v2, pre2[y][0] = x;
			}
			else if(dis2[y][1] > v1+v2)
			{
				dis2[y][2] = dis2[y][1], pre2[y][2] = pre2[y][1];
				dis2[y][1] = v1+v2, pre2[y][1] = x;	
			}	
			else if(dis2[y][2] > v1+v2)
			{
				dis2[y][2] = v1+v2, pre2[y][2] = x;
			}
		}
	}
	rep(i,1,tl)
	{
		int u = line[i].u;
		int v = line[i].v;
		int w = line[i].w;
		rep(k1,0,2)
			rep(k2,0,2)
				cal(u,v,k1,k2,w);

		rep(k1,0,2)
			rep(k2,0,2)
				cal(v,u,k1,k2,w);
	}
}

int main()
{
	scanf("%d%d%d",&n,&m,&k);
	init();
	rep(i,1,m)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z), add(y,x,z);
		line[++tl].u = x, line[tl].v = y, line[tl].w = z;
	}
	if(k > m || k >= n || k > 5){
		printf("-1\n");
		return 0;
	}
	ans = inf;
	while(k < 5){
		add(n,n+1,0), add(n+1,n,0);
		line[++tl].u = n, line[tl].v = n+1, line[tl].w = 0;
		n++, k++;
	} 
	bfs();
	if(ans == inf)
		printf("-1\n");
	else printf("%d\n",ans);
	return 0;
}

/*
4 6 3
1 2 10
2 3 1
3 4 1
2 4 10
1 3 1
1 4 1

*/

猜你喜欢

转载自blog.csdn.net/qq_41552508/article/details/82351819