BZOJ 1576: [Usaco2009 Jan]安全路经Travel

日常自闭半小时后看题解,太弱了qwq。

感觉这道题还是比较难的,解法十分巧妙,不容易想到。

首先题目说了起点到每个点的最短路都是唯一的,那么对这个图求最短路图必定是一棵树,而且这棵树是唯一的。

那么我们求出了这棵最短路树,每当删掉一条边,其实这条边就是这个点与它父亲结点的连边,那么我们肯定是会 选择一些非树边连上这个点 让这个点连回最短路树。那么我们倒过来考虑,想想每一条非树边能对答案造成什么贡献?

当一条非树边(x,y)连上了最短路树,会与x,y的LCA形成一个环,而且 P = dis[x]+dis[y]+w = 2*dis[LCA(x,y)]+环长度  这似乎和答案没什么关系,其实 P-dis[y] 就是 ans[x]的一个答案选择,同理P-dis[x]也是ans[y]的一个选择(选择的意思就是一条满足条件的路径,不经过被删边)。那么我们可以得到一个朴素算法:枚举每条非树边,计算所有的ans[x]和ans[y]的选择,然后选一个最小的即可。

显然朴素算法会超时。那么这里就用到一个十分巧妙的方法:观察上式我们发现都有一个共同的东西P=dis[x]+dis[y]+w 。然后这个东西再减去dis[x]或者dis[y]。那么对于某个点x,既然减去的东西一样都是ans=min(P-dis[y]) 那么显然当P最小的时候,答案有最优解。

那么我们得到这样一个算法,先按P=dis[x]+dis[y]+w排序,按P从小到大去更新答案,这样就不用暴力更新找最小值。

而且按上诉方式得到的答案是最优的,那么每个点只要更新一次就好了,所以我们还可以用树上的并查集加速这个非树边向上跳跃更新答案的过程。(就是当对于点x更新完后,把x的并查集合并到x的父亲结点)。

至此问题得到解决。

细节详见代码:

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int N=2e5+10;
int n,m,tot,x[N],y[N],z[N],dis[N],fa[N],pre[N],ans[N];
struct edge{
    int x,y,z;
    bool operator < (const edge &rhs) const {
        return dis[x]+dis[y]+z<dis[rhs.x]+dis[rhs.y]+rhs.z;
    }
}e[N<<1];

int cnt=1,head[N],to[N<<1],nxt[N<<1],len[N<<1];
void add_edge(int x,int y,int z) {
    nxt[++cnt]=head[x]; to[cnt]=y; len[cnt]=z; head[x]=cnt;
}

bool vis[N];
priority_queue<pii> q;
void Dijkstra() {
    memset(dis,0x3f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    dis[1]=0; q.push(make_pair(0,1));
    while (!q.empty()) {
        pii u=q.top(); q.pop();
        if (vis[u.second]) continue;
        vis[u.second]=1;
        for (int i=head[u.second];i;i=nxt[i]) {
            int y=to[i];
            if (dis[y]>dis[u.second]+len[i]) {
                dis[y]=dis[u.second]+len[i];
                q.push(make_pair(-dis[y],y));
            }
        }
    }
}

int getfa(int x) { return x==fa[x] ? x : fa[x]=getfa(fa[x]); }

void solve(int x,int y,int w) {  //树上并查集 
    while (getfa(x)!=getfa(y)) {
        x=getfa(x); y=getfa(y);     //这一步很重要 
        if (dis[x]>dis[y]) swap(x,y);  
        ans[y]=w-dis[y];
        fa[y]=fa[pre[y]];
        y=pre[y];    //交替向上跳 
    }
}

int main()
{
    cin>>n>>m;
    for (int i=1;i<=m;i++) {
        scanf("%d%d%d",&x[i],&y[i],&z[i]);
        add_edge(x[i],y[i],z[i]); add_edge(y[i],x[i],z[i]);
    }
    
    Dijkstra();
    
    for (int i=1;i<=m;i++)
        if (dis[x[i]]+z[i]==dis[y[i]]) pre[y[i]]=x[i];
        else if (dis[y[i]]+z[i]==dis[x[i]]) pre[x[i]]=y[i];
        else { e[++tot].x=x[i]; e[tot].y=y[i]; e[tot].z=z[i]; }
    sort(e+1,e+tot+1);
    
    for (int i=1;i<=n;i++) fa[i]=i;
    for (int i=1;i<=tot;i++)
        solve(e[i].x,e[i].y,dis[e[i].x]+dis[e[i].y]+e[i].z);
    for (int i=2;i<=n;i++) printf("%d\n",ans[i] ? ans[i] : -1);
    return 0;    
} 

猜你喜欢

转载自www.cnblogs.com/clno1/p/10822109.html
今日推荐