心灵的抚慰(heart.pas/c/cpp)
Problem: heart.pas/c/cpp
Input: heart.in
Output: heart.out
Memory Limit: 256 MB
Time Limit: 1 sec
背景 Background
病毒问题解决后,神牛们的心灵久久不能平静。有个神牛因此已经“乱了”。他脑子中满是程序(否则怎么会成为神牛呢),而且他可以从一个程序联想到一些相似的程序。比如从程序1联想到2,从2联想到4,从4联想到6,从6联想到9……躺就像搜索一样一步一步越陷越深。不过同一种联想他只会联想一次。比如1、2之间他进行了一次联想,那么他不会再重新联想1到2,或2到1。眼看他又要乱了,有人突然想到,如果他刚开始时想到的程序能够经过联想若干次后联想回到原程序,那不就乱回来了吗?由于神牛马上就要开乱,请在1秒内告诉他,他需要想哪个程序,以便乱回来。
题目描述 Description
给出一些程序和他们互相联想的关系(如果两个程序A、B有联系,神牛可以从A联想到B,也可以从B联想到A,但A、B之间神牛最多联想一次),请告诉神牛他需要想哪个程序,以便在最短的时间内乱回来,并输出这个最短时间。
输入格式 Input Format (heart.in)
第一行有两个正整数N,M,分别表示程序个数和有多少对程序可以被神牛直接互相联想。
以下M行,每行三个正整数,分别表示一种联想的两端的程序的编号(从1开始),以及进行这种联想所需要的最短时间。
输出格式 Output Format (heart.out)
如果神牛无论如何都再也乱不回来了,输出“He will never come back.”。
如果神牛能够乱回来,请输出神牛会乱多长时间。
样例输入 Input Example
4 3
1 2 10
1 3 20
1 4 30
样例输出 Output Example
He will never come back.
数据范围
对于100% 的数据,n≤250。
题目意思很明了,是要我们求出最小环,当然就不能重复了
这里介绍两种方法
方法一:搜索——万能、高效的方法
在想不出怎么做的时候,搜索多少都是可以拿一点分的,毕竟(欧教说的)啥子算法都是枚举!
在深搜的时候我们需要记录如下状态dfs(x,sum),x表示当前正在哪一点,而sum表示当前已经用的最少时间,那怎么判断构成环了呢?我们可以定义一个全局变量,也可以加一个参数tar表示需要达到的目标位置,那么就只需要判断x=tar是就表示构成环了。
那在刚开始搜的时候不就是x=tar吗?所以我们还需要一个标记,true为当前不是第一步(就表示可以进行判断),而false就表示当前是刚开始搜,不能判断环
所以,最终我们就把状态设计成了四维的 dfs(x,flag,sum,tar)
至于怎么判断路是否走过了,可以开一个二维hash,h[i][j]表示i->j已经走过,当然标记的时候要双向标记,既要标记f[i][j],也要标记f[j][i]
都知道深搜效率是很低的,所以必须剪枝!判断是否可走我们已经进行了可行性剪枝,接下来我们只需要在dfs开头加一句最优化剪枝即可
加上最优化剪枝的程序可是比方法二的动规还快一点哦~(不过也不是所有)
C++ Code
/*
C++ Code
http://oijzh.cnblogs.com
By jiangzh
*/
#include<cstdio>
#include<cstring>
#define MAXN 260
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
int n,m,dis[MAXN][MAXN];
bool map[MAXN][MAXN];
bool h[MAXN][MAXN];
int ans=0x3f3f3f3f;
void dfs(int x,bool flag,int sum,int tar)//x 当前点 flag 是否为第一个点 sum 目前总和 tar 目标点
{
if(sum>ans) return;
if(flag && x==tar) {ans=min(ans,sum);return;}
for(int i=1;i<=n;i++)
if(map[x][i] && !h[x][i])
{
h[x][i]=h[i][x]=true;
dfs(i,true,sum+dis[x][i],tar);
h[x][i]=h[i][x]=false;
}
}
int main()
{
freopen("heart.in","r",stdin);
freopen("heart.out","w",stdout);
scanf("%d%d",&n,&m);
memset(dis,0x3f,sizeof(dis));
int x,y,z;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&z);
map[x][y]=map[y][x]=true;
dis[x][y]=dis[y][x]=z;
}
for(int i=1;i<=n;i++)
dfs(i,false,0,i);
if(ans==0x3f3f3f3f) printf("He will never come back.");
else printf("%d",ans);
return 0;
}
方法二:floyd+动规求最小环——简单高效
都知道floyd可以求出任意点到任意点的最短距离,那么这一题求最小环当然也可以用floyd
不过从题目中可知,走过的路是不能再走的,所以我们要保证不重复
先说说floyd的原理,其状态其实是三维的f[i][j][k](只是把k就地滚动了),表示 i-->j 这条路用 1..k 这些点做中转所能取得的最短路,那么根据动规的思想,我们把它分成两类 i-->j用1..k-1做中转 和 i-->j用k做中转 ,用下面这一张图具体解释
dis[][]表示用floyd更新的最短路,而map[][]表示原来的长度
如果我们加上 k 这一状态,那么途中绿色部分就是 i-->j经过1..k-1作中转的最短路,那么现在我们用上 k 这个点我们只需要连上 i-k 和 k-j 就能构成一个环,由于dis[i][j]只用了1..k-1来做中转,而map[i][k]和map[k][j]用了k,所以路径是肯定不会重复的
用这个方法要特别注意,我赋初值习惯0x3f(pascal好像是$3f),结果后面加爆了,所以最多只能0x07(pascal是$07)!!!
C++ Code
/*
C++ Code
http://oijzh.cnblogs.com
By jiangzh
*/
#include<cstdio>
#include<cstring>
#define MAXN 260
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
int n,m,dis[MAXN][MAXN];
int map[MAXN][MAXN];
int ans=0x07070707;
int main()
{
freopen("heart.in","r",stdin);
freopen("heart.out","w",stdout);
scanf("%d%d",&n,&m);
memset(dis,0x07,sizeof(dis));
memset(map,0x07,sizeof(map));
int x,y,z;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&z);
map[x][y]=map[y][x]=z;
dis[x][y]=dis[y][x]=z;
}
for(int k=1;k<=n;k++)
{
for(int i=1;i<k;i++)
for(int j=i+1;j<k;j++)
ans=min(ans,dis[i][j]+map[i][k]+map[k][j]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
if(ans==0x07070707) printf("He will never come back.");
else printf("%d",ans);
return 0;
}