luogu 【p1119】灾后重建

来源:互联网 发布:js拖动滑块实现验证码 编辑:程序博客网 时间:2024/06/08 14:20

灾后重建

题目背景

B地区在地震过后,所有村庄都造成了一定的损毁,而这场地震却没对公路造成什么影响。但是在村庄重建好之前,所有与未重建完成的村庄的公路均无法通车。换句话说,只有连接着两个重建完成的村庄的公路才能通车,只能到达重建完成的村庄。

题目描述

给出B地区的村庄数N,村庄编号从0到N-1,和所有M条公路的长度,公路是双向的。并给出第i个村庄重建完成的时间t[i],你可以认为是同时开始重建并在第t[i]天重建完成,并且在当天即可通车。若t[i]为0则说明地震未对此地区造成损坏,一开始就可以通车。之后有Q个询问(x, y, t),对于每个询问你要回答在第t天,从村庄x到村庄y的最短路径长度为多少。如果无法找到从x村庄到y村庄的路径,经过若干个已重建完成的村庄,或者村庄x或村庄y在第t天仍未重建完成 ,则需要返回-1。

输入输出格式

输入格式:
输入文件rebuild.in的第一行包含两个正整数N,M,表示了村庄的数目与公路的数量。

第二行包含N个非负整数t[0], t[1], …, t[N – 1],表示了每个村庄重建完成的时间,数据保证了t[0] ≤ t[1] ≤ … ≤ t[N – 1]。

接下来M行,每行3个非负整数i, j, w,w为不超过10000的正整数,表示了有一条连接村庄i与村庄j的道路,长度为w,保证i≠j,且对于任意一对村庄只会存在一条道路。

接下来一行也就是M+3行包含一个正整数Q,表示Q个询问。

接下来Q行,每行3个非负整数x, y, t,询问在第t天,从村庄x到村庄y的最短路径长度为多少,数据保证了t是不下降的。

输出格式:
输出文件rebuild.out包含Q行,对每一个询问(x, y, t)输出对应的答案,即在第t天,从村庄x到村庄y的最短路径长度为多少。如果在第t天无法找到从x村庄到y村庄的路径,经过若干个已重建完成的村庄,或者村庄x或村庄y在第t天仍未修复完成,则输出-1。

输入输出样例

输入样例#1:
4 5
1 2 3 4
0 2 1
2 3 1
3 1 2
2 1 4
0 3 5
4
2 0 2
0 1 2
0 1 3
0 1 4
输出样例#1:
-1
-1
5
4
说明

对于30%的数据,有N≤50;

对于30%的数据,有t[i] = 0,其中有20%的数据有t[i] = 0且N>50;

对于50%的数据,有Q≤100;

对于100%的数据,有N≤200,M≤N*(N-1)/2,Q≤50000,所有输入数据涉及整数均不超过100000。

这道题加深了我对Floyed算法的理解。
首先谈谈Floyed算法:Floyd算法的本质是动态规划,其转移方程为:f[k][i][j] = min( f[k-1][i][j], f([k-1][i][k])+f[k-1][k][j] )。
f[k][i][j]表示路径除开起点i与终点j,只经过前k个点中的某些点,从i到j的最小值。计算这个值只需要考虑两种情况:最短路经过k,和最短路不经过k(那么就经过前k-1个点中的某些点)。由于k要从k-1转移而来,自然k为最外层的循环。而经过滚动(类似于背包问题)后,就变成了我们熟悉的f[i][j]=min(f[i][j],f[i][k]+f[k][j])了。

思路一(30分,TLE七个点):
对于每一次询问,都把小于当钱时间的点和边加入到图中,然后每一次询问都跑一边Floyed,这样做肯定会超时啊

//如果输入数据没有将时间排序,需要先排序,这点很重要 //每次询问都跑Floyed肯定会超时 #include<cstdio>#include<cstring>using namespace std;bool b[201];//某村庄是否修好 int n,m,x,y,z,q,u,v,time,t[201],t1[201],f[201][201],matrix[201][201];//t1存不大于当前时间的点的编号,作为中转站,matrix只用来存原图 int main(){    memset(f,0x7f,sizeof(f));    memset(matrix,0x7f,sizeof(matrix));    scanf("%d%d",&n,&m);    for (int i=0; i<n; i++)//注意从0开始         scanf("%d",&t[i]);    for (int i=1; i<=m; i++)    {        scanf("%d%d%d",&x,&y,&z);        matrix[x][y]=matrix[y][x]=z;    }    scanf("%d",&q);    for (int l=1; l<=q; l++)    {        int num=0;        scanf("%d%d%d",&u,&v,&time);        for (int i=0; i<n; i++)            if (t[i]<=time){ t1[++num]=i; b[i]=true; }//找到在当前时间之内可以修好的村庄,可以作为中转站         for (int i=0; i<n; i++)//如果两个村庄都修建好了,就把它们之间的最短距离初始化为两点间的距离             for(int j=0; j<n; j++)                if (b[i]&&b[j]) f[i][j]=f[j][i]=matrix[i][j];        for (int k=1; k<=num; k++)        {            for (int i=0; i<n; i++)                for (int j=0; j<n; j++)                {                    if (f[i][j]>f[i][t1[k]]+f[t1[k]][j]&&i!=j&&i!=t1[k]&&t1[k]!=j&&f[i][t1[k]]<0x7f7f7f7f&&f[t1[k]][j]<0x7f7f7f7f)//需要加那些限制条件?                        f[i][j]=f[i][t1[k]]+f[t1[k]][j];                }        }        if (f[u][v]==0x7f7f7f7f) printf("-1\n");        else printf("%d\n",f[u][v]);    }    return 0;}

思路二(100分):改变思路:看中转点k每一次自增能解决什么问题,能回答问题就输出。还要开一个bool数组b[201],标记某村庄是否已经作为中转点,可以从n^3q的复杂度变为 n^3+q的复杂度,这样还是可以接受的。
代码如下:

//改变思路:看k每一次自增能解决什么问题 //所以要将提出的问题存到一个数组里 #include<cstdio>#include<cstring>using namespace std;bool b[201];int n,m,x,y,z,q,t[201],f[201][201];int from[50001],to[50001],day[50001];//questions;int main(){    memset(f,0x7f,sizeof(f));    scanf("%d%d",&n,&m);    for (int i=0; i<n; i++)        f[i][i]=0;//初始化     for (int i=0; i<n; i++)//注意从0开始         scanf("%d",&t[i]);    for (int i=1; i<=m; i++)    {        scanf("%d%d%d",&x,&y,&z);        f[x][y]=f[y][x]=z;    }    scanf("%d",&q);    int tot=0;    for (int i=1; i<=q; i++)    {        scanf("%d%d%d",&from[i],&to[i],&day[i]);    }    for (int l=1; l<=q; l++)    {        for (int k=0; k<n; k++)            if (t[k]<=day[l]&&!b[k])            {                b[k]=1;                for (int i=0; i<n; i++)                    for (int j=0; j<n; j++)                        if (f[i][j]>f[i][k]+f[k][j]&&i!=j&&i!=k&&k!=j&&f[i][k]<0x7f7f7f7f&&f[k][j]<0x7f7f7f7f)                            f[i][j]=f[i][k]+f[k][j];            }        if (t[from[l]]<=day[l]&&t[to[l]]<=day[l]&&f[from[l]][to[l]]!=0x7f7f7f7f)            printf("%d\n",f[from[l]][to[l]]);        else printf("-1\n");    }    return 0;}

所以说编程也是美的。代码以境界为最上,有境界则自成高格,自有名句。宽嫂之代码所以独绝者在此:http://blog.csdn.net/Cansult/article/details/77628415