1. 程式人生 > >BZOJ 1576: [Usaco2009 Jan]安全路經Travel

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

wap air namespace scanf 最短 size opera urn 最短路

日常自閉半小時後看題解,太弱了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; }

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