对于校赛F题的一些思考

来源:互联网 发布:天池竞赛用什么算法 编辑:程序博客网 时间:2024/06/05 23:59

F Minpath

Time Limit:1000MS  Memory Limit:65536K
Total Submit:61 Accepted:10

Description




一只兔子要从点0捡到许多金子,它要回到家点n(图中点11),但它只能通过1到n-1石板才能到家,石板分为多层,每层必须经过一个石板(如图,1、2、3、4为一层,5、6、7为一层,8、9、10为一层),从一个石板到另一个石板有一定距离,兔子要从点0到跳到家,经过的最短距离是多少。

Input

输入有多组测试实例。 
每组实例第一行为n(1 < n < 10^4),m(0 < m < n),n表示家所在的位置,m为层数(点0,点n不算在层数内)。第二行开始为每层信息。 
每层第一行有两个整数N、M,N(N < 100)为该层石板数,M(M < 10^4)为上层与该层的连接边数,第二行为该层结点(每个点对应一个石板),每两个结点用空格隔开,第三行到第(M-2)为边信息。每行有三个数x,y,v,表示x到y的距离为v(v < 1000,如果 <= 0,表示此边不可通过)。总边数不会超过10^6. 
默认最后一层的点到家的距离为1. 

Output

每组实例输出一个整数表示最短路径,如果兔子回不了家,输出 -1。

Sample Input

3 12 21 20 1 30 2 5

Sample Output

4




比赛的时候看到F题,心中立马生出一种亲切的感觉。虽然题目加了很多条件,但是本质上仍然是一个数塔。数塔是我参加去年暑假集训的时候,学长教我们的第一道题,实际上,我也是从那时候开始接触算法的。那么关于这道题的分析,就先从数塔开始。
http://acm.zzuli.edu.cn/JudgeOnline/showproblem?problem_id=1165
这是学校OJ上的数塔。数塔作为DP的入门题型,算法上并没有什么需要特别注意的地方,就是从底层往上层逐层相加排除,最后的结果就是最大值。但是当时我觉得这个算法简直太厉害了,如果暴力的话,数塔产生的数据量是2^n,这里的n是数塔的层数。这样在数塔达到一百层时,不管是时间上还是空间上都已经不允许用暴力的解法去解决了。
#include<stdio.h>
#include<string.h>
#define N 105
int Max(int x,int y)
{
return x>y?x:y;
}
int main()
{
int map[N][N];
int i,j;
int t,n;
scanf("%d",&t);
while(t--)
{
memset(map,0,sizeof(map));
scanf("%d",&n);


for(i=1;i<=n;i++)
{
for(j=1;j<=i;j++)
scanf("%d",&map[i][j]);
}


for(i=n-1;i>=1;i--)
{
for(j=1;j<=i;j++)
map[i][j]=Max(map[i][j]+map[i+1][j],map[i][j]+map[i+1][j+1]);
}
printf("%d\n",map[1][1]);
}
return 0;
}
这是我在学校OJ上AC的代码。
后来做题的时候遇到过一道叫做免费馅饼的题,做免费馅饼的时候我觉得免费馅饼和数塔的算法很类似,都是通过一层一层的递进,都是对每一个数据的更新定义一种方式,虽然题目看起来完全不同,但是本质上是一样的。
http://acm.hdu.edu.cn/showproblem.php?pid=1176
这是杭电上免费馅饼的题目。
我当时将两道题做了一下对比,然后就有一种想法,不管是数塔还是免费馅饼,都是将输入的数据存入一个二维数组中从下向上计算,那么能不能反过来,让他们从上往下计算,在理论上这完全是可行的,只不过对数塔而言,这样在运算结束后需要将最后一行数据逐一比较,选出其中最大的数据,多了一次甄选最大值的过程。
这并不能提高代码的运算效率,看起来好像没什么意义。其实不然,这样做有一个非常大的好处,就是数据输入的时候是从上往下输入的,如果运算也是从上往下运算,那么数据在输入之后直接参加运算,计算出结果后数据就没用了,可以直接抛弃,不需要记录,这样就节省了大量的存储空间。对于一个一百层的数塔,数组从map[105][105]变成map[105],可能还看不出来有太大的作用,但是如果把数塔加到一万层,那么节省的空间就很可观了。
下面是用这种方式在OJ上AC的代码:
#include<stdio.h>
#include<string.h>
#define N 105
int Max(int x,int y)
{
return x>y?x:y;
}
int main()
{
int map[N],temp[N];
int i,j;
int t,n;
int ans;
int a;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
memset(map,0,sizeof(map));
memset(temp,0,sizeof(temp));
for(i=1;i<=n;i++)
{
for(j=1;j<=i;j++)
{
scanf("%d",&a);
temp[j]=Max(map[j-1]+a,map[j]+a);
}
for(j=1;j<=i;j++)
map[j]=temp[j];//需要加一个temp数组记录计算结果,再复制给map数组,否则前面的计算会修改map的值,影响之后的计算。
}


ans=-1;
for(i=1;i<=n;i++)
{
if(map[i]>ans)
ans=map[i];
}
printf("%d\n",ans);
}
return 0;
}
好,下面我们进入正题,这次比赛的F题数塔的特征太明显了。如果仅仅是一个数塔,那么明显不够这么多牛人秒的,所以这个数塔比起常规数塔有一个最大的不同,就是它的数据最大是10000层,如果按照常规的算法,先将所有输入的数据放入一个二维数组中,然后再逐层计算,那么MLE几乎是必然的,实际上,这也是很多队伍最后没有A掉这道题最主要的原因。但是如果我们采用第二种方法,啧啧,数据量很大?一个10000大小的数组足矣。
http://acm.zzuli.edu.cn/JudgeOnline/showproblem?problem_id=2003
这是比赛F题的地址。
这是在OJ上AC的F题的代码:
#include<stdio.h>
#include<string.h>
#define N 10005
int Min(int x,int y)
{
return x<y?x:y;
}
int main()
{
int mark[N],temp[N];
int n,m;
int n1,m1;
int i;
int x,y,z;
int ans;
int a;
while(scanf("%d%d",&n,&m)!=EOF)
{
memset(mark,-1,sizeof(mark));
mark[0]=0;
while(m--)
{
scanf("%d%d",&n1,&m1);
for(i=0;i<n1;i++)
scanf("%d",&temp[i]);
while(m1--)
{
scanf("%d%d%d",&x,&y,&z);
if(z<=0)
continue;
if(mark[x]!=-1)
{
if(mark[y]==-1)
mark[y]=mark[x]+z;
else
mark[y]=Min(mark[y],mark[x]+z);
}
}
}


ans=99999999;
for(i=0;i<n1;i++)
{
if(mark[temp[i]]!=-1)
{
a=mark[temp[i]];
a++;
ans=Min(ans,a);
}
}


printf("%d\n",ans);
}
return 0;
}
内存244K。
本来说好的晚上看数据结构的,结果到现在书还都没翻开。