NKOJ 3579 (NOI 2010)海拔(平面圖最小割+對偶圖)

P3579【NOI2010】海拔

問題描述

YT市是一個規劃良好的城市,城市被東西向和南北向的主幹道劃分爲n×n個區域。簡單起見,可以將YT市看作一個正方形,每一個區域也可看作一個正方形。從而,YT城市中包括(n+1)×(n+1)個交叉路口和2n×(n+1)條雙向道路(簡稱道路),每條雙向道路連接主幹道上兩個相鄰的交叉路口。下圖爲一張YT市的地圖(n = 2),城市被劃分爲2×2個區域,包括3×3個交叉路口和12條雙向道路。
這裏寫圖片描述

小Z作爲該市的市長,他根據統計信息得到了每天上班高峯期間YT市每條道路兩個方向的人流量,即在高峯期間沿着該方向通過這條道路的人數。每一個交叉路口都有不同的海拔高度值,YT市市民認爲爬坡是一件非常累的事情,每向上爬h的高度,就需要消耗h的體力。如果是下坡的話,則不需要耗費體力。因此如果一段道路的終點海拔減去起點海拔的值爲h(注意h可能是負數),那麼一個人經過這段路所消耗的體力是max{0, h}(這裏max{a, b}表示取a, b兩個值中的較大值)。小Z還測量得到這個城市西北角的交叉路口海拔爲0,東南角的交叉路口海拔爲1(如上圖所示),但其它交叉路口的海拔高度都無法得知。小Z想知道在最理想的情況下(即你可以任意假設其他路口的海拔高度),每天上班高峯期間所有人爬坡所消耗的總體力和的最小值。

輸入格式

第一行包含一個整數n,含義如上文所示。 接下來4n(n + 1)行,每行包含一個非負整數分別表示每一條道路每一個方向的人流量信息。輸入順序:n(n + 1)個數表示所有從西到東方向的人流量,然後n(n + 1)個數表示所有從北到南方向的人流量,n(n + 1)個數表示所有從東到西方向的人流量,最後是n(n + 1)個數表示所有從南到北方向的人流量。對於每一個方向,輸入順序按照起點由北向南,若南北方向相同時由西到東的順序給出(參見樣例輸入)。

輸出格式

僅包含一個數,表示在最理想情況下每天上班高峯期間所有人爬坡所消耗的總體力和(即總體力和的最小值),結果四捨五入到整數。

樣例輸入

1
1
2
3
4
5
6
7
8

樣例輸出

3

樣例說明

樣例數據見下圖。
這裏寫圖片描述
最理想情況下所有點的海拔如上圖所示。
對於20%的數據:n ≤ 3;
對於50%的數據:n ≤ 15;
對於80%的數據:n ≤ 40;
對於100%的數據:1 ≤ n ≤ 500,0 ≤ 流量 ≤ 1,000,000且所有流量均爲整數。


拿到這題感覺很嚇人,每個點海拔還能是分數,但是有一點比較顯然,那就是任意點的海拔都在[0,1] 之間。
進一步的,感覺似乎每個點的海拔都應該是整數,那麼不妨假設每個點海拔都是0,1 之一,不妨來驗證一下。
假設有三個點A,B,C 存在A>B>C 的路徑,那麼對於A>C 人,體力消耗等於|AC| ,對於A>B ,消耗爲sumAB|AB| ,對於B>C ,消耗爲sumBC|BC| ,我們發現,最優情況下B 的海拔總是要等於A 的海拔或者C 的海拔。由於原圖中給定了兩個點的海拔,那麼圖中每個點的海拔必然是01

再進一步觀察,最優情況下必然01 構成了兩個聯通塊,他們之間有一條分界線,而最終的總體力消耗就是從0走到1的人的體力消耗之和。即分界線上有向流量之和。這個根據上面所說的就比較顯然了。

因此一個直接的想法是以左上角爲源點,以右下角爲匯點跑一個最小割,這個最小割就是答案。但直接這麼做複雜度是n6 級別的,肯定過不了。

平面圖最小割可以轉化成對偶圖最短路,所以這題直接轉化成對偶圖求最短路,建對偶圖的時候由於是有向邊,因此要特別注意連邊的方向,這個畫個圖手算一下就能看出來。

關於平面圖最小割轉化成對偶圖最短路,只需要連接源點和匯點將原來無界的一個面拆成兩個,形成一個額外的面,然後原圖的面當成點,相鄰的面之間連邊,邊權等於兩個面的公共邊的容量。然後刪掉直接無界面和額外面之間的邊。具體可以百度。


代碼:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#define N 505
#define M 5000000
using namespace std;
typedef pair<int,int> par;
int n,S,T,WE[N][N],EW[N][N],NS[N][N],SN[N][N];
int TOT,LA[N*N],NE[M],EN[M],LE[M];
int dis[N*N];
priority_queue<par>Q;
void ADD(int x,int y,int z)
{
    TOT++;
    EN[TOT]=y;
    LE[TOT]=z;
    NE[TOT]=LA[x];
    LA[x]=TOT;
}
int Gid(int x,int y)
{
    if(x==0||y==n+1)return S;
    if(x==n+1||y==0)return T;
    return (x-1)*n+y;
}
void Dijkstra(int s)
{
    int i,x,y,d;par t;
    memset(dis,60,sizeof(dis));
    dis[s]=0;Q.push(par(0,s));
    while(Q.size())
    {
        t=Q.top();Q.pop();
        x=t.second;
        d=-t.first;
        if(dis[x]!=d)continue;
        for(i=LA[x];i;i=NE[i])
        {
            y=EN[i];
            if(dis[y]>d+LE[i])
            {
                dis[y]=d+LE[i];
                Q.push(par(-dis[y],y));
            }
        }
    }
}
int main()
{
    int i,j,k,x,y,z;
    scanf("%d",&n);S=n*n+1;T=S+1;
    for(i=1;i<=n+1;i++)
    for(j=1;j<=n;j++)scanf("%d",&x),ADD(Gid(i-1,j),Gid(i,j),x);
    for(i=1;i<=n;i++)
    for(j=1;j<=n+1;j++)scanf("%d",&x),ADD(Gid(i,j),Gid(i,j-1),x);
    for(i=1;i<=n+1;i++)
    for(j=1;j<=n;j++)scanf("%d",&x),ADD(Gid(i,j),Gid(i-1,j),x);
    for(i=1;i<=n;i++)
    for(j=1;j<=n+1;j++)scanf("%d",&x),ADD(Gid(i,j-1),Gid(i,j),x);
    Dijkstra(S);
    printf("%lld",dis[T]);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章