1. 程式人生 > 實用技巧 >訓練指南第五章-圖論 刷題記錄

訓練指南第五章-圖論 刷題記錄

5.3.3 最短路例題選講

UVA1376 Animal Run

思路:網格圖,如果要讓左上角沒法到右下角的話,肯定滿足有一條連貫的障礙,從左邊或者下邊到右邊或者上邊,這樣才能做到把終點包圍起來。顯然是最小割,但是網路流複雜度看上去並不能過。於是考慮建模,然後問題轉化成了 “左/下 \(\to\) 右/上”的最短路問題。

由於是個網格圖,考慮對每個三角形建一個虛點,兩個三角形相鄰處的邊權就是對應點連邊的邊權。對左邊界和下邊界建立超級源點,對右邊界和上邊界同樣建立超級匯點,網格圖邊界上的三角形和源/匯相連,邊權是邊界上的邊。最後跑最短路即可。

圖大概長這樣:Pinta 一直閃退就沒畫下去。

有一說一這個建圖真的麻煩

這題在 UVA 上一直過不去……瘋狂 TLE ,但是程式碼在 這道題 的洛谷和黑暗爆炸都過去了……最後發現是 ed 的值設小了,導致超級匯點編號和三角虛點混了起來……洛谷資料真的弱。

//Author: RingweEH
void input()
{
    for ( int i=1; i<m; i++ )
        add( i*2,ed,read() );
    for ( int i=2; i<n; i++ )
     for ( int j=1; j<m; j++ )
     {
        int tmp=read(),t1=2*(i-2)*(m-1)-1+2*j,t2=2*(i-1)*(m-1)+2*j;
        add( t1,t2,tmp ); add( t2,t1,tmp );
     }
    //------------------------------------------------------
    for ( int i=1; i<m; i++ )
        add( st,2*(n-2)*(m-1)-1+2*i,read() );
    for ( int i=1; i<n; i++ )
     for ( int j=1; j<=m; j++ )
     {
        int t1=2*(i-1)*(m-1)-1+2*j,t2=t1-1,tmp=read();
        if ( j==1 ) add( st,t1,tmp );
        else if ( j==m ) add( t2,ed,tmp );
        else add( t1,t2,tmp ),add( t2,t1,tmp );
     }
    //-------------------------------------------------------
    for ( int i=1; i<n; i++ )
     for ( int j=1; j<m; j++ )
     {
        int t1=2*(i-1)*(m-1)-1+2*j,t2=t1+1;
        int tmp=read(); add( t1,t2,tmp ); add( t2,t1,tmp );
     }
}

int Dijkstra( int S )
{
    for ( int i=1; i<=ed; i++ )
        dis[i]=inf;
    dis[S]=0; q.push( node(0,S) );
    while ( !q.empty() )
    {
        int u=q.top().pos; q.pop();
        vis[u]=1;
        for ( int i=head[u]; i; i=e[i].nxt )
        {
            int v=e[i].to;
            if ( dis[v]>dis[u]+e[i].val )
            {
                dis[v]=dis[u]+e[i].val;
                if ( !vis[v] ) q.push( node(dis[v],v) );
            }
        }
    }
    return dis[ed];
}

void init()
{
    st=0; ed=(2*n-1)*(m-1)+1;
    memset( vis,0,sizeof(vis) ); tot=0;
    memset( head,0,sizeof(head) );
    while ( !q.empty() ) q.pop();
}

5.4 生成樹相關

5.4.0 一些性質和基本做法

切割性質

如果所有邊權不相同, \(S\) 為既非空集也非全集的 \(V\) 的子集,邊 \(e\) 是滿足一個端點在 \(S\) 內,一個不在 \(S\) 內的所有邊中權值最小的一個,則所有生成樹均包含 \(e\) .

迴路性質

如果所有邊權均不相同,設 \(C\) 是圖中任意迴路,\(e\)\(C\) 上權值最大的邊,則 \(G\) 所有生成樹均不包含 \(e\) .

增量最小生成樹

從包含 \(n\) 個點的空圖開始,依次加入 \(m\) 條帶權邊,求最小生成樹權值。

每次求出新的生成樹之後,把其他邊刪除,這樣之後只需要計算 \(n\)

邊圖的最小生成樹。根據迴路性質,加入一條邊之後,刪除環上邊權最大的邊即可。複雜度 \(O(nm)\)

最小瓶頸生成樹

加權無向圖,求生成樹使得最大邊權儘可能小。

直接 \(\text{Kruskal}\) 即可得到一種方案。

最小瓶頸路

給定加權無向圖上 \(u,v\) 兩點,求 \(u\to v\) 的一條路徑使得最長邊儘可能短。

先求最小生成樹,那麼起點和終點在樹上的路徑即為所求。

次小生成樹

(不嚴格小於)

列舉要加的新邊。在最小生成樹上加一條邊 \(u\to v\) 後,把樹上路徑中最長邊刪除即為所求。那麼可以按照最小瓶頸路的求法,\(O(n^2)\) 求兩點間最大邊權,\(O(m)\) 列舉 \(O(1)\) 計算答案。

最小有向生成樹

給定有向帶權圖 \(G\) 和一個節點 \(u\) ,找一個以 \(u\) 為根節點,權和最小的樹形圖。(根可以到達每個點,除根外入度為1)

朱-劉 演算法。

預處理,刪除自環並判斷無解。

給所有非根節點選擇一條權值最小的入邊,如果沒有構成圈那麼就形成了答案;否則把圈縮點,繼續。

但是有個問題,如果 \(X\) 在圈中已經有了入弧 \(Y\to X\) ,收縮之後又選了 \(Z\to X\) ,那麼必須把 \(Y\to X\) 減去,等價於在 \(Z\to X\) 的權值中去掉 \(Y\to X\)

UVA1494 Qin Shi Huang's National Road System

思路: 徐福太不行了只能修一條路 先求出最小生成樹,然後預處理每兩個點之間的路徑最大邊權。然後列舉每一條邊作為徐福的邊,用次小生成樹的思想替換掉就好了。 cf在講什麼東西看不懂

//Author: RingweEH
void add( int u,int v,db dis )
{
    e[++tot].to=v; e[tot].nxt=head[u]; head[u]=tot; e[tot].dis=dis;
    e[++tot].to=u; e[tot].nxt=head[v]; head[v]=tot; e[tot].dis=dis;
}

void dfs( int u,int fat,db val )
{
    for ( int i=1; i<=n; i++ )
        if ( vis[i] && (i^u) )  mxc[u][i]=mxc[i][u]=max( mxc[i][fat],val );
    for ( int i=head[u]; i; i=e[i].nxt )
    {
        int v=e[i].to;
        if ( v==fat || vis[v] ) continue; 
        vis[v]=1; dfs( v,u,e[i].dis );
    }
}
//--------------------------main part-----------------------------
        int cnt=0;
        for ( int i=1; i<=n; i++ )
         for ( int j=i+1; j<=n; j++ )
            if ( i^j ) { cnt++; r[cnt].dis=get_dis(i,j); r[cnt].num1=i; r[cnt].num2=j; }
        //------------------------MST--------------------
        sort( r+1,r+1+cnt );
        for ( int i=1; i<=n; i++ )
            fa[i]=i;
        int cnt2=0; db sum=0;
        for ( int i=1; i<=cnt; i++ )
        {
            int u=r[i].num1,v=r[i].num2; u=find(u); v=find(v);
            if ( u==v ) continue;
            fa[u]=v; add( r[i].num1,r[i].num2,r[i].dis ); cnt2++; sum+=r[i].dis;
            if ( cnt2==n-1 ) break;
        }
        //-------------------Kruskal Finished----------------------
        vis[1]=1; dfs( 1,0,0.0 ); db ans=0;
        for ( int i=1; i<=n; i++ )
         for ( int j=i+1; j<=n; j++ )
         {
            db B=sum-mxc[i][j],A=c[i].person+c[j].person;  ans=max( ans,A/B );
         }

UVA11354 Bond

思路:第一眼:瓶頸路??第二眼:\(n=5e4\) ???Game Over

考慮和倍增 LCA 一樣的思路。令 \(fa[i][j]\) 為第 \(2^j\) 祖先,\(mxc[i][j]\)\(i\to fa[i][j]\) 的最大權值。然後查詢就是和LCA一樣的。

人沒了啊……一直是 WA ,調了半個晚上快瘋掉了。留坑罷……我都把樣例和 udebug 的資料全過了。

update 給你講個笑話, for ( int i=21; i; i-- )for ( int i=21; i>=0; i-- ) 竟然是不等價的呢。

交得 UVA 都 Submit Failed 了,換了 這裡 交過去的。

//Author: RingweEH
void dfs( int u,int fat,int val )
{
    for ( int i=head[u]; i ;i=e[i].nxt )
    {
        int v=e[i].to;
        if ( v==fat ) continue;
        fa[v][0]=u; mxc[v][0]=e[i].val; dep[v]=dep[u]+1;
        dfs( v,u,e[i].val );
    }
}

void LCA_init()
{
    for ( int j=1; (1<<j)<n; j++ )
     for ( int i=1; i<=n; i++ )
     {
        fa[i][j]=fa[fa[i][j-1]][j-1]; 
        mxc[i][j]=max( mxc[i][j-1],mxc[fa[i][j-1]][j-1] );
     }
}

int LCA( int u,int v )
{
    if ( dep[u]<dep[v] ) swap( u,v );
    int res=-1010;
    for ( int i=21; i>=0; i-- )
        if ( dep[u]-(1<<i)>=dep[v] ) res=max( res,mxc[u][i] ),u=fa[u][i];
    if ( u==v ) return res;
    for ( int i=21; i>=0; i-- )
        if ( fa[u][i]!=fa[v][i] )
        {
            res=max( res,max(mxc[u][i],mxc[v][i]) ); 
            u=fa[u][i]; v=fa[v][i]; 
        }
    res=max( res,max(mxc[u][0],mxc[v][0]) );
    return res;
}

void Kruskal()
{
    sort( r+1,r+1+m ); int cnt=0;
    for ( int i=1; i<=n; i++ )
        f[i]=i;
    for ( int i=1; i<=m; i++ )
    {
        int u=r[i].fro,v=r[i].to,w=r[i].val; u=find(u),v=find(v);
        if ( u==v ) continue; f[u]=v; add( r[i].fro,r[i].to,w ); cnt++;
        if ( cnt==n-1 ) break;
    }
}