1. 程式人生 > 實用技巧 >P2939 [USACO09FEB]Revamping Trails G 題解 && 分層圖入門講解

P2939 [USACO09FEB]Revamping Trails G 題解 && 分層圖入門講解

考前補坑系列。之前懂分層圖原理但是從來沒寫過,現在寫一下。


題目傳送門:

解題意:

給你一張非常正常的,n個點m條雙向邊的圖,每條邊有一個dis值,讓你求1到n的最短路。

與普通最短路不同之處在於:在上述條件上,再加入一個k,在圖中選取k條邊使得邊權變為0.

讓你求最短路。

講思路:

這裡不再贅述正常的找思路過程,因為本文的目的不是解題而是為了介紹一個演算法。

分層圖,即字面意思,在原圖的基礎上,建立許許多多和原圖完全相同的圖。

你設想一下,把這些圖豎著堆疊起來:

原圖為上圖的其中一層,3個點大小的圖。

我們要建立k層圖,在上圖中,k=2。

在此可能產生這樣的問題:為啥要建k層

題目中說要進行k次的把邊變成0的操作,而題目往往不會給你模板讓你去打,要不就是在模板的基礎上魔改,要不就是結合多種演算法求解。我們所要做的往往就是根據題意,進行準確的演算法建模

。在這道題中,與最短路不同的性質就是使k條邊邊權為0,我們要做的就是建出適合這個特殊性質的模型使得可以求解:

我們將上一層的from結點與下一層的to結點連邊權為0雙向邊:

設最上層的圖為原圖,即第一層,因為層間的邊邊權為0,那麼從第一層走到第二層的效果是:經過了一個邊權為0的邊,而其他的邊邊權依舊是原來的邊權

沒錯,這也就代表著我們使用了一次使邊免費的機會。

接著往後推:走到了第三層就代表著使用了2次邊權免費的機會......走到了第k+1層就使用的k次機會。

至此,我們完成了切合題意的建模。

接下來考慮求解答案:因為可以使用k次,那麼我們就從使用0次到使用k次的匯點(n)的dis值中取min即可。

分層圖的點的編號只要保證可以用唯一的法則推出每一個點在每一層對應的編號即可。

自認為可讀性高的程式碼:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
#define N 10006
#define inf 2147483647
#define ll long long
using namespace std;
int read()
{
    int ans=0;
    char ch=getchar(),last=' ';
    while(ch>'
9'||ch<'0')last=ch,ch=getchar(); while(ch>='0'&&ch<='9')ans=(ans<<3)+(ans<<1)+ch-'0',ch=getchar(); return last=='-'?-ans:ans; } int n,m,k,hea[210007],num,u,v,w,dis[210007],vis[210007]; struct edg{ int nex,to,dis; }edge[4100006]; struct node{ int num,dis; bool operator < (const node &b)const{ return dis>b.dis; } }; inline void add(int from,int to,int dis) { num++; edge[num].dis=dis; edge[num].nex=hea[from]; edge[num].to=to; hea[from]=num; } inline void dij() { memset(dis,0x3f,sizeof(dis)); dis[1]=0; priority_queue<node> q; q.push((node){1,0}); while(!q.empty()) { node kk=q.top();q.pop(); int now=kk.num; if(vis[now])continue; vis[now]=1; for(int i=hea[now];i;i=edge[i].nex) { int v=edge[i].to; if(dis[v]>dis[now]+edge[i].dis) { dis[v]=dis[now]+edge[i].dis; q.push((node){v,dis[v]}); } } } } int main(){ n=read();m=read(),k=read(); for(int i=1;i<=m;i++) { u=read(),v=read(),w=read(); add(u,v,w);add(v,u,w); for(int j=1;j<=k;j++) { add(j*n+u,j*n+v,w); add(j*n+v,j*n+u,w); add((j-1)*n+u,j*n+v,0); } } int ans=inf; dij(); for(int i=0;i<=k;i++) { ans=min(ans,dis[(i+1)*n]); } printf("%d\n",ans); return 0; }

完結撒花,希望對各位有所幫助。

NOIP2020 衝鴨