【JZOJ4597】现世斩

Description

异变又发生了,魂魄妖梦作为幻想乡的一名自(cheng)机(guan),主动前去解决异变。
我们用一个n个点、m条边的无向联通图来表示妖梦可选择的路线,妖梦从白玉楼出发,白玉楼被视为编号为1的点,编号为2——n的点是幻想乡的村庄,其中编号为n的村庄发生了异变。
每条边上可能会有一些妖怪袭击人类(然而妖梦是半人半灵),所以对于第i条边,妖梦需要t[i]分钟通过这条路。妖梦带了她的人符[现世斩],可以使所有连接点x的边的通过时间变成1(x可以任意指定)。然而为了保留足够的力量解决异变,妖梦只会用这个符卡一次。妖梦想知道,她到达村庄n的最短时间是多少。

Solution

跟GDOI2016第二天的第一题好像啊!
不过我换了一种方法。
先从1到n做一遍dij,然后再从n到1做一遍dij。
每次枚举一个中间点x,然后求出min{d[1,u]}和min{d[v,n]},u和v与x直接相连,最后用d[1,u]+d[v,n]+2来更新最优答案就行。复杂度O(nm)。

Code

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int maxn=100005,maxm=1000005;

typedef long long LL;

int n,m,tot,h[maxn],e[maxm],next[maxm],len[maxm],pos[maxn],id[maxn];

LL f1[maxn],f2[maxn],ans;

char c;

int read()
{
    for (c=getchar();c<'0' || c>'9';c=getchar());
    int x=c-48;
    for (c=getchar();c>='0' && c<='9';c=getchar()) x=x*10+c-48;
    return x;
}

void add(int x,int y,int l)
{
    e[++tot]=y; next[tot]=h[x]; len[tot]=l; h[x]=tot;
}

void Up(int x,LL *f)
{
    while (x>1 && f[id[x/2]]>f[id[x]])
    {
        id[x/2]^=id[x]^=id[x/2]^=id[x];
        pos[id[x]]=x;
        x/=2;
    }
    pos[id[x]]=x;
}

void Down(int x,LL *f)
{
    while (x*2<=tot)
    {
        int son=(x*2==tot || f[id[x*2]]<f[id[x*2+1]])?x*2:x*2+1;
        if (f[id[x]]<=f[id[son]]) break;
        id[x]^=id[son]^=id[x]^=id[son];
        pos[id[x]]=x;
        x=son;
    }
    pos[id[x]]=x;
}

void Dij(int x,LL *f)
{
    for (int i=1;i<=n;i++) f[i]=1e16;
    f[x]=0;
    tot=1;
    id[1]=x; pos[x]=1;
    for (int i=1;i<=n;i++) if (i!=x)
    {
        id[++tot]=i; pos[i]=tot;
    }
    for (int i=1;i<=n;i++)
    {
        int x=id[1];
        id[1]=id[tot--]; pos[id[1]]=1;
        Down(1,f);
        for (int j=h[x];j;j=next[j]) if (f[x]+len[j]<f[e[j]])
        {
            f[e[j]]=f[x]+len[j]; Up(pos[e[j]],f);
        }
    }
}

int main()
{
    freopen("cut.in","r",stdin); 
    freopen("cut.out","w",stdout);
    n=read(); m=read();
    while (m--)
    {
        int x=read(),y=read(),l=read();
        add(x,y,l); add(y,x,l);
    }
    Dij(1,f1); Dij(n,f2);
    ans=f1[0]=f2[0]=1e16;
    for (int i=h[1];i;i=next[i]) ans=min(ans,f2[e[i]]+1);
    for (int i=h[n];i;i=next[i]) ans=min(ans,f1[e[i]]+1);
    for (int i=2;i<n;i++)
    {
        int x=0,y=0;
        for (int j=h[i];j;j=next[j])
        {
            if (f1[e[j]]<f1[x]) x=e[j];
            if (f2[e[j]]<f2[y]) y=e[j];
        }
        ans=min(ans,f1[x]+f2[y]+2);
    }
    printf("%lld\n",ans);
    fclose(stdin); fclose(stdout);
    return 0;
}

你可能感兴趣的:(最短路,SPFA,迪杰斯特拉算法,dij,现世斩)