vijos 1006 晴天小猪历险记之Hill——数字三角形的终极变化

时间:2022-02-04 04:22:14

题目链接:https://vijos.org/p/1006

数字三角形原题看这里:http://www.cnblogs.com/huashanqingzhu/p/7326837.html

背景

在很久很久以前,有一个动物村庄,那里是猪的乐园(^_^),村民们勤劳、勇敢、善良、团结……
不过有一天,最小的小小猪生病了,而这种病是极其罕见的,因此大家都没有储存这种药物。所以晴天小猪自告奋勇,要去采取这种药草。于是,晴天小猪的传奇故事便由此展开……

描述

这一天,他来到了一座深山的山脚下,因为只有这座深山中的一位隐者才知道这种药草的所在。但是上山的路错综复杂,由于小小猪的病情,晴天小猪想找一条需时最少的路到达山顶,但现在它一头雾水,所以向你求助。

山用一个三角形表示,从山顶依次向下有1段、2段、3段等山路,每一段用一个数字T(1<=T<=100)表示,代表晴天小猪在这一段山路上需要爬的时间,每一次它都可以朝左、右、左上、右上四个方向走。山是环形的。(**注意**:在任意一层的第一段也可以走到本层的最后一段或上一层的最后一段)。

晴天小猪从山的左下角出发,目的地为山顶,即隐者的小屋。

★★★**本题为vijos早年陈题,描述晦涩,现重新描述题面如下**★★★
有一个数字三角形,共nn行,依次编号为第一行,第二行至第nn行。其中第ii行有ii个数字,位置依次记为(i,1),(i,2)(i,1),(i,2)到(i,i)(i,i)。
现在从第nn层的第一个位置出发(即(n,1)),每一步移到相邻的,且行编号小于或等于当前行编号的一个位置中,直到(1,1)结束,在不重复经过任何位置的情形下,路过的所有位置(包括端点)的对应数字之和最小。

下面详细定义相邻关系。
同一层内连续的两个位置相邻,特别的有每一层第一个位置与最后一个位置相邻。
对于位置(i,j),它与(i-1,j-1)以及(i-1,j)相邻,特别的(i,1)与(i-1,i-1)相邻,且(i,i)与(i-1,1)相邻。

格式

输入格式

第一行有一个数n(2<=n<=1000),表示山的高度。

从第二行至第n+1行,第i+1行有i个数,每个数表示晴天小猪在这一段山路上需要爬的时间。

输出格式

一个数,即晴天小猪所需要的最短时间。

样例1

样例输入1

5
1
2 3
4 5 6
10 1 7 8
1 1 4 5 6

样例输出1

10

限制

各个测试点1s

提示

在山的两侧的走法略有特殊,请自己模拟一下,开始我自己都弄错了……

来源

Sunnypig

算法分析:

参考:

http://blog.csdn.net/Little_Flower_0/article/details/47945611

https://vijos.org/p/1006/solution

http://www.cnblogs.com/candy99/p/5981533.html

vijos 1006  晴天小猪历险记之Hill——数字三角形的终极变化

 算法一:

建立图,然后求最短路径。(来源

(从上往下建)

 #include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>
using namespace std;
typedef long long ll;
const int N=*/,INF=1e9+;
inline int read(){
char c=getchar();int x=,f=;
while(c<''||c>''){if(c=='-')f=-;c=getchar();}
while(c>=''&&c<=''){x=x*+c-'';c=getchar();}
return x*f;
}
int n,t[N],u,v,w;
inline int id(int i,int j){
return i*(i-)/+j;
}
struct edge{
int v,ne;
}e[N<<];
int cnt=,h[N];
inline void add(int u,int v){
cnt++;
e[cnt].v=v;e[cnt].ne=h[u];h[u]=cnt;
}
inline void ins(int u,int v){
add(u,v);add(v,u);
}
int d[N],inq[N];
queue<int> q;
void spfa(int s){
int m=id(n,n);
for(int i=;i<=m;i++) d[i]=INF;
d[s]=;
q.push(s);
while(!q.empty()){
int u=q.front();q.pop();
inq[u]=;
for(int i=h[u];i;i=e[i].ne){
int v=e[i].v,w=t[u];
if(d[v]>d[u]+w){
d[v]=d[u]+w;
if(!inq[v]){inq[v]=;q.push(v);}
}
}
}
}
int main(){
n=read();
for(int i=;i<=n;i++){
if(i!=) ins(id(i,),id(i,i));
if(i<n&&i!=){
add(id(i,i),id(i+,));
add(id(i,),id(i+,i+));
}
for(int j=;j<=i;j++){
t[id(i,j)]=read();
if(j<i) ins(id(i,j),id(i,j+));
if(i<n) add(id(i,j),id(i+,j)),add(id(i,j),id(i+,j+));
}
}
spfa(id(,));
printf("%d",d[id(n,)]+t[id(n,)]);
}

或者参考下面的这一段代码:

最短路很容易~
但是建图有点麻烦~~
因为考虑到如果从一个点往上连边的话
会有点小麻烦(可能不存在~)
那么我们可以从每一个点开始
每一个可以走到它的点向他连边
即向下找往上连
如果是在一行的首位置或者末位置就有5种被走上来的方法
否则四种走法
这个需要很小心注意一个地方写错了就gg(调了半小时)
好坑的地方就是这里的右上方=上方....
害怕~
然后用个等差数列求和公式求出对应的顶标就好啦~

代码来源:https://vijos.org/p/1006/solution

 #include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std; const int MAXn=;
const int MAXN=;
const int MAXM=;
const int INF=(<<)-;
struct Edge
{
int to,w,next;
}e[MAXM];
int fisrt[MAXN];//Edges
queue<int> q;
int d[MAXN],in[MAXN];//SPFA
int w[MAXn][MAXn];
int l,n,tot; inline void Add_Edge(int x,int y,int w)
{
e[++tot].to=y; e[tot].w=w;
e[tot].next=fisrt[x]; fisrt[x]=tot;
} inline int getn(int x,int y)
{
return x*(x-)/+y;
} void init()
{
memset(fisrt,-,sizeof(fisrt));
scanf("%d",&l); n=getn(l,l);
for(int i=;i<=l;i++)
for(int j=;j<=i;j++)
scanf("%d",&w[i][j]);
} void getmap()//建图从上向下建更方便
{
Add_Edge(,,w[][]);
Add_Edge(,,w[][]);
for(int i=;i<=l;i++)
for(int j=;j<=i;j++)
{
int u=getn(i,j);
if(j==)
{
Add_Edge(getn(i,i),u,w[i][i]);
Add_Edge(getn(i,j+),u,w[i][j+]);
if(i!=l)
Add_Edge(getn(i+,i+),u,w[i+][i+]),
Add_Edge(getn(i+,j),u,w[i+][j]),
Add_Edge(getn(i+,j+),u,w[i+][j+]);
}
else if(j==i)
{
Add_Edge(getn(i,j-),u,w[i][j-]);
Add_Edge(getn(i,),u,w[i][]);
if(i!=l)
Add_Edge(getn(i+,j),u,w[i+][j]),
Add_Edge(getn(i+,),u,w[i+][]),
Add_Edge(getn(i+,j+),u,w[i+][j+]);
}
else
{
Add_Edge(getn(i,j-),u,w[i][j-]);
Add_Edge(getn(i,j+),u,w[i][j+]);
if(i!=l)
Add_Edge(getn(i+,j),u,w[i+][j]),
Add_Edge(getn(i+,j+),u,w[i+][j+]);
}
}
} void SPFA(int s)
{
memset(d,0x37,sizeof(d));
q.push(s); d[s]=; in[s]=;
while(!q.empty())
{
int u=q.front(); q.pop(); in[u]=;
for(int i=fisrt[u];i!=-;i=e[i].next)
{
int& v=e[i].to; int& w=e[i].w;
if(d[v]>d[u]+w)
{
d[v]=d[u]+w;
if(!in[v])
{
q.push(v);
in[v]=;
}
}
}
}
cout<<d[]+w[][]<<endl;
} int main()
{
init();
getmap();
SPFA(getn(l,));
}

 算法二:动态规划

说实话,上面那一长段的关于动规算法的描述,我是真没看懂,直接看代码还大概清楚一点。

下面代码中,f[i][j]表示从(i,j)这个位置到达顶部(1,1)这个位置的最短距离。

 #include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std; const int maxn=;
int a[maxn][maxn];
int f[maxn][maxn];
int n; int main()
{
cin>>n;
for(int i=;i<=n;i++)
for(int j=;j<=i;j++)
cin>>a[i][j];
f[][]=a[][];//终点处就直接是该点时间
for(int i=;i<=n;i++)//一层一层往上推
{
for(int j=;j<i;j++)//先求出从上一层推出来的最小值
f[i][j]=min(f[i-][j],f[i-][j-])+a[i][j];
f[i][]=min(f[i-][],f[i-][i-])+a[i][];//特殊边界点处理
f[i][i]=min(f[i-][i-],f[i-][])+a[i][i];//特殊边界点处理
//同一层更新最优解
for(int k=i-;k>;k--)//从右往左推 从右往左走的情况更新
f[i][k]=min(f[i][k],f[i][k+]+a[i][k]);
f[i][i]=min(f[i][i],f[i][]+a[i][i]); for(int l=;l<=i;l++)//从左往右推 从左往右走的情况更新
f[i][l]=min(f[i][l],f[i][l-]+a[i][l]);
f[i][]=min(f[i][],f[i][i]+a[i][]);
//我也没太明白为何需要下面这两个for,把上面的事情再做一遍。我把下面这两个for屏蔽以后确实是可以AC的。假如真有原因,估计要读上面那一长段文字了
for(int k=i-;k>;k--)//再推一遍从右往左推 从右往左走的情况更新
f[i][k]=min(f[i][k],f[i][k+]+a[i][k]);
f[i][i]=min(f[i][i],f[i][]+a[i][i]); for(int l=;l<=i;l++)//再推一遍从左往右推 从左往右走的情况更新
f[i][l]=min(f[i][l],f[i][l-]+a[i][l]);
f[i][]=min(f[i][],f[i][i]+a[i][]);
}
cout<<f[n][]<<endl;
}

上面这一段代码来自vijos讨论版块:https://vijos.org/p/1006/solution

下面是另一个动规代码,可以与上面这一段互相对照理解。代码来源:http://blog.csdn.net/Little_Flower_0/article/details/47945611

下面这段代码中,d[i][j]表示从(i,j)这个位置到达底层的最短距离。

 #include<stdio.h>
#define maxint 2000000000
#define min(a,b) (a<b?a:b)
long a[][]={};
long d[][]={};
int main()
{
long n,i,j;
scanf("%ld",&n);
for(i=;i<n;i++)
for(j=;j<=i;j++)
scanf("%ld",&a[i][j]); for(i=;i<n;i++)
for(j=;j<=i;j++)
d[i][j]=maxint; for(i=;i<n;i++) //对最后一行的处理
d[n-][i]=a[n-][i];
for(i=;i<n;i++)
d[n-][i]=d[n-][i-]+a[n-][i]; //因为最后一行右边的点只能从左边的推来,所以有了这个预处理
for(i=n-;i>=;i--)
d[n-][i]=min(d[n-][i],d[n-][(i+)%n]+a[n-][i]); //往左走的话,肯定要先从左边翻过去再向左走 for(i=n-;i>=;i--)
{
d[i][]=min(d[i+][],d[i+][]); //对左边界的处理
d[i][]=min(d[i][],d[i+][i+]);
d[i][]+=a[i][]; d[i][i]=min(d[i+][],d[i+][i]); //对右边界的处理
d[i][i]=min(d[i][i],d[i+][i+]);
d[i][i]+=a[i][i]; for(j=;j<=i-;j++) //对中间位置的处理,这时候下面的一行已经处理完了
d[i][j]=min(d[i+][j],d[i+][j+])+a[i][j]; d[i][]=min(d[i][],d[i][i]+a[i][]); //左推与右推
for(j=;j<=i;j++)
d[i][j]=min(d[i][j],d[i][j-]+a[i][j]);
for(j=i;j>=;j--)
d[i][j]=min(d[i][j],d[i][(j+)%(i+)]+a[i][j]); }
printf("%ld\n",d[][]);
return ;
}