GMOJ5430. 图 题解

%%%高嘉煊巨佬为我们带来的质量极高的题目,和文采极佳的题解

\(n\) 个点, \(m\) 条边的无向图,每条边有额外的信息 \(c\) , \(r\) ,有 \(q\) 组询问,每组询问四个参数 \(u\) , \(v\) , \(l\) , \(r\) 。表示一开始你在点 \(u\) 上,然后按顺序处理编号从 \(l\)\(r\) 的边。对于第 \(i\) 条边,如果你当前在该边的端点上,那么你可以移动到另一个端点且代价为 \(c\) ,如不在该边端点或不移动则代价为 \(r\) 。询问到达 \(v\) 点的最小代价,如果不能到达,输出 -1 。

\(n\le30,m\le20000,q\le200000\)

思路

首先考虑 整体二分也就考虑了我整场比赛

对边二分,考虑已经处理出一个 \(mid\)

对于一个跨过 \(mid\) 的询问,我们 DP 出 \(fl_{i,j,k}(i\le mid)\) 表示从第 \(mid\) 条边到第 \(i\) 条边的从 \(j\) 出发到 \(k\) 的最小代价,以及 \(fr_{i,j,k}(i>mid)\) 表示从第 \(mid+1\) 条边到第 \(i\) 条边的从 \(j\) 出发到 \(k\) 的最小代价。

因为最短路是双向的所以并不需要考虑顺序正反。

然后枚举中间第 \(mid\) 条边结束后的点,合并一下两边的权值即可。

接下来对询问整体二分。

实现

首先就是对于 \(r\)\(mid\) 重合的情况要特殊处理,以及一些其他的边界问题。

处理 \(fl\)\(fr\) 数组时只用赋一层的初值,否则会 T 飞。

代码

含少量注释。

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int n,m,k,e[20010][4],q[200010][5],le[200010][5],ri[200010][5],fl[20010][51][51],fr[20010][51][51],ans[200010];
char BuF[1<<26],*InF=BuF;
template<typename T>void read(T &x){
    bool f=1;
    for(;47>*InF||*InF>58;f^=*InF++=='-');
    for(x=0;47<*InF&&*InF<58;x=(x<<3)+(x<<1)+(*InF++^48));
    x=f?x:-x;
}
void divc(int l,int r,int ql,int qr){
    if(ql>qr){
        return;
    }
    register int mid=(l+r)>>1,cntl=0,cntr=0;
    memset(fl[mid+1],63,sizeof(fl[mid+1]));    //只赋一层初值
    for(register int i=1;i<=n;++i){
        fl[mid+1][i][i]=0;
    }
    for(register int i=mid;i>=l;--i){
        for(register int j=1;j<=n;++j){
            for(register int k=1;k<=n;++k){
                fl[i][j][k]=fl[i+1][j][k]+e[i][3];    //直接赋值,确保INF可以正常转移
                if(k==e[i][0]||k==e[i][1]){
                    fl[i][j][k]=min(fl[i][j][k],fl[i+1][j][e[i][k==e[i][0]]]+e[i][2]);    //经过第i条边
                }
            }
        }
    }
    memset(fr[mid],63,sizeof(fr[mid]));
    for(register int i=1;i<=n;++i){
        fr[mid][i][i]=0;
    }
    for(register int i=mid+1;i<=r;++i){
        for(register int j=1;j<=n;++j){
            for(register int k=1;k<=n;++k){
                fr[i][j][k]=fr[i-1][j][k]+e[i][3];
                if(k==e[i][0]||k==e[i][1]){
                    fr[i][j][k]=min(fr[i][j][k],fr[i-1][j][e[i][k==e[i][0]]]+e[i][2]);
                }
            }
        }
    }
    for(register int i=ql;i<=qr;++i){    //我这里使用的没有等于的划分,因为可以等于也可以在该层计算无需递归
        if(q[i][3]<mid){
            memcpy(le[++cntl],q[i],sizeof(q[i]));    //向左划分
        }else if(q[i][2]>mid){
            memcpy(ri[++cntr],q[i],sizeof(q[i]));    //向右划分
        }else if(q[i][3]>mid){    //对于右端点超出mid
            for(int j=1;j<=n;++j){
                ans[q[i][4]]=min(ans[q[i][4]],fl[q[i][2]][j][q[i][0]]+fr[q[i][3]][j][q[i][1]]);
            }
        }else{    //右端点与mid重合
            ans[q[i][4]]=fl[q[i][2]][q[i][1]][q[i][0]];
        }
    }
    for(register int i=1;i<=cntl;++i){
        memcpy(q[ql+i-1],le[i],sizeof(le[i]));
    }
    for(register int i=1;i<=cntr;++i){
        memcpy(q[qr-i+1],ri[i],sizeof(ri[i]));
    }
    if(l<r){
        divc(l,mid,ql,ql+cntl-1);
        divc(mid+1,r,qr-cntr+1,qr);
    }
}
int main(){
    freopen("graph.in","r",stdin);
    freopen("graph.out","w",stdout);
    fread(BuF,1,1<<26,stdin);
    read(n);read(m);read(k);
    for(register int i=1;i<=m;++i){
        read(e[i][0]);read(e[i][1]);read(e[i][2]);read(e[i][3]);
    }
    for(register int i=0;i<k;++i){
        read(q[i][0]);read(q[i][1]);read(q[i][2]);read(q[i][3]);q[i][4]=i;
    }
    memset(ans,63,sizeof(ans));
    divc(1,m,0,k-1);
    for(register int i=0;i<k;++i){
        printf("%d\n",ans[i]<0x3f3f3f3f?ans[i]:-1);
    }
    fclose(stdin);
    fclose(stdout);
    return(0);
}
posted @ 2021-02-02 22:30  ToUNVRSe  阅读(33)  评论(0)    收藏  举报