1. 程式人生 > >「Nescafé26」 Freda的傳呼機 【最短路徑+書上倍增】

「Nescafé26」 Freda的傳呼機 【最短路徑+書上倍增】

試驗 一位 bre 最短路 isp add 交流 實現 continue

題目:

為了隨時與rainbow快速交流,Freda制造了兩部傳呼機。Freda和rainbow所在的地方有N座房屋、M條雙向光纜。每條光纜連接兩座房屋,傳呼機發出的信號只能沿著光纜傳遞,並且傳呼機的信號從光纜的其中一端傳遞到另一端需要花費t單位時間。現在Freda要進行Q次試驗,每次選取兩座房屋,並想知道傳呼機的信號在這兩座房屋之間傳遞至少需要多長時間。Freda和rainbow簡直弱爆了有木有T_T,請你幫幫他們吧……
N座房屋通過光纜一定是連通的,並且這M條光纜有以下三類連接情況:
A:光纜不形成環,也就是光纜僅有N-1條。
B:光纜只形成一個環,也就是光纜僅有N條。
C:每條光纜僅在一個環中

頌芬數據占10%,2<=N<=1000,N-1<=M<=1200。
A類數據占30%,M=N-1。
B類數據占50%,M=N。
C類數據占10%,M>N。
對於100%的數據,2<=N<=10000,N-1<=M<=12000,Q=10000,1<=x,y<=N,1<=t<32768。

分析:

(對於直接想要AC的人,可以直接忽略此部分)

可以看到:對於10%的數據,可以簡簡單單跑一個SPFA,(但一定要註意細節,嚴格按照模板來),下面給出10分的代碼(通往AC的路是循序漸進的):

技術分享
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
struct point
{
    int to,nxt,w;
}edge[52010];
int n,m,Q,a,b,ww,cnt=0
; int head[52010],vis[52010]; long long dis[22010]; void init() { memset(head,0,sizeof(head)); cnt=0; } void add(int u,int v,int wei) { cnt++; edge[cnt].to=v; edge[cnt].nxt=head[u]; edge[cnt].w=wei; head[u]=cnt; } void spfa(int st) { int fron=0,tail=1; int q[120000]; memset(vis,
0,sizeof(vis)); memset(dis,0x7f,sizeof(dis)); vis[st]=1; dis[st]=0; q[1]=st; do { fron++; int tt=q[fron]; vis[tt]=0; for(int i=head[tt];i;i=edge[i].nxt) { int v=edge[i].to; if(dis[v]>dis[tt]+edge[i].w) { dis[v]=dis[tt]+edge[i].w; if(!vis[v]) { vis[v]=1; tail++; q[tail]=v; } } } }while(fron!=tail); } int main() { cin>>n>>m>>Q; init(); for(int i=1;i<=m;i++) { cin>>a>>b>>ww; add(a,b,ww); add(b,a,ww); } while(Q--) { cin>>a>>b; spfa(a); cout<<dis[b]<<endl; } return 0; }
View Code

剩下的能跑出樹上倍增的,相信離成功也不遠了。仔細看看題目中紅色的字體,會發現實際上所有的環只可能有公共頂點,不可能有公共邊,這樣畫出來就很像一個仙人掌(其實名稱都不重要),那麽我們該如何處理這樣的一個個環呢?其實可以想到,我們以每一個公共頂點為樹根,可以把多個環轉化成一棵樹,其中樹枝長就是環中每個點到頂點的最短距離,但一定要分別記錄每個點從兩邊到環頂的距離 l[i] 和 r[i],因為轉換成一棵樹後,從樹上看來似乎是每兩個點之間的路徑必過頂點,但實際上在環中兩個點完全可以不通過頂點而相互到達,因此兩個點若在一個環中(這裏實現的時候用一個數組分別記錄每個點所在的環的編號和環頂),就有:

dis[x][y]= min( l[x]+r[y] , l[y]+r[x] , abs(r[x]-r[y]) );//一左一右到環頂,一右一左到環頂,和不通過環頂

然後至於樹上倍增,我們用fa[x][i]表示從x這個節點往上2^i步能到的節點,用dis[x][i]表示從x這個節點到fa[x][i]這個祖先的距離;

就有初始化(遞推):

fa[x][i]=fa[fa[x][i-1]][i-1];//從上一個位置再走一步
dis[x][i]=dis[fa[x][i-1]][i-1]+dis[x][i-1];//走半截再走半截

於是此題就可以AC了:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn=121000;
struct point1
{
    int to,nxt,w;
}edge[maxn<<1];
struct point2
{
    int to,nxt,w;
}edge2[maxn<<1];
int n,m,Q,a,b,ww,ss=0,ttt=0,n_cir=0;
int lop[maxn][4];//詳見62~65行 
int e[maxn][4],pre[maxn][3],bin[20],dfn[maxn],dep[maxn],head1[maxn],head2[maxn]; 
/*pre[i][0/1],0為i前一個是誰,1為i前一條邊長*/ 
int dis[maxn][20],fa[maxn][20];

void init()
{
    bin[0]=1;
    for(int i=1;i<=18;i++)
        bin[i]=bin[i-1]<<1;
}

int cnt=0;
void add(int u,int v,int wei)
{
    edge[cnt].to=v;
    edge[cnt].nxt=head1[u];
    edge[cnt].w=wei;
    head1[u]=cnt++;
}

int ct=0;
void ins(int u,int v,int wei)
{
    edge2[ct].to=v;
    edge2[ct].nxt=head2[u];
    edge2[ct].w=wei;
    head2[u]=ct++;
    
}

void dfs(int x,int y,int fa)
{
    ttt++;
    dfn[x]=ttt;
    for(int i=head1[x];~i;i=edge[i].nxt)
    {
        int v=edge[i].to;
        if(v==fa && i==(y^1)) continue;
        if(dfn[v]!=0 && dfn[v]<dfn[x])//找到一個環
        {
            int len=edge[i].w;
            n_cir++;
            for(int j=ss;pre[j][0]!=v;j--)
                len+=pre[j][1];
            lop[x][0]=n_cir;//環的編號
            lop[x][1]=v;//環的頂點 
            lop[x][2]=edge[i].w;//從一邊到頂點的距離 
            lop[x][3]=len-lop[x][2];//從另一邊 
            ins(v,x,min(lop[x][2],lop[x][3]));//正反建圖
            ins(x,v,min(lop[x][2],lop[x][3]));//按最短路徑重新建圖 
            for(int j=ss-1;pre[j][0]!=v;j--)
            {
                int z=pre[j][0];
                lop[z][0]=n_cir;
                lop[z][1]=v;
                lop[z][2]=lop[pre[j+1][0]][2]+pre[j+1][1];
                lop[z][3]=len-lop[z][2];
                ins(v,z,min(lop[z][2],lop[z][3]));
                ins(z,v,min(lop[z][2],lop[z][3]));
            } 
        } 
        if(dfn[v])
            continue;
        pre[++ss][0]=v;
        pre[ss][1]=edge[i].w;
        dfs(v,i,x);
    }
    ss--;
}

void dfss(int x)
{
    for(int i=1;i<=18;i++)
    {
        if(dep[x]<bin[i]) break;
        fa[x][i]=fa[fa[x][i-1]][i-1];
        dis[x][i]=dis[fa[x][i-1]][i-1]+dis[x][i-1];
    }
    for(int i=head2[x];~i;i=edge2[i].nxt)
    {
        if(edge2[i].to!=fa[x][0])
        {
            int v=edge2[i].to;
            fa[v][0]=x;
            dep[v]=dep[x]+1;//深度在待會lca要用 
            dis[v][0]=edge2[i].w;
            dfss(v);
        }
    }
}

int lca(int x,int y)
{
    int sum=0;
    if(dep[x]<dep[y]) swap(x,y);
    int t=dep[x]-dep[y];
    for(int i=0;i<=18;i++)
        if(t&bin[i])//y在x下面,t的二進制在i那一位上有1 (保證要用2^i湊齊t)
        {
            sum+=dis[x][i];
            x=fa[x][i];
        }
    for(int i=18;i>=0;i--)
    {
        if(fa[x][i]!=fa[y][i])
        {
            sum+=dis[x][i]+dis[y][i];
            x=fa[x][i];
            y=fa[y][i];
        }
    }
    if(x==y) return sum;
    if(lop[x][0]==lop[y][0] && lop[x][0]!=0)
        sum+=min(min(lop[x][2]+lop[y][3],lop[y][2]+lop[x][3]),abs(lop[x][2]-lop[y][2]));
    else
        sum+=dis[x][0]+dis[y][0];
    return sum;
}

int main()
{
    memset(head1,-1,sizeof(head1));
    memset(head2,-1,sizeof(head2));
    init();
    cin>>n>>m>>Q;
    for(int i=1;i<=m;i++)
    {
        cin>>a>>b>>ww;
        add(a,b,ww);
        add(b,a,ww);
        e[i][1]=a;
        e[i][2]=b;
        e[i][3]=ww;
    }
    ss=1;
    pre[1][0]=1;
    pre[1][1]=0;
    dfs(1,-1,0);//找環,並計算出每個點到環頂的最短路 
    for(int i=1;i<=m;i++)
    {
        int aa=e[i][1];
        int bb=e[i][2];
        int cc=e[i][3];
        if( (lop[aa][0]!=lop[bb][0] || !lop[aa][0] || !lop[bb][0]) && lop[aa][1]!=bb && lop[bb][1]!=aa)
        {
            ins(aa,bb,cc);
            ins(bb,aa,cc);    
        } 
    } 
    dfss(1);//倍增處理 
    while(Q--)
    {
        cin>>a>>b;
        cout<<lca(a,b)<<endl;
    }
    return 0;
}

「Nescafé26」 Freda的傳呼機 【最短路徑+書上倍增】