BZOJ1001狼抓兔子(平面圖最小割)
題目大意:給一個n*m的網格圖,表示一個地圖。起點(1,1),終點(n,m)。每條邊上有一個權值,表示該路徑上的兔流量。現在一些兔子從起點沿著邊跑到終點。然後有一些大灰狼要抓這些兔子。一隻狼能抓一隻兔子。現在狼王想知道至少要派多少狼能堵住兔子的路。
題目分析:很裸的最小割問題。條件反射最大流搞之。但是這題的資料量是10M。n,m也比較大。硬上了一個最大流果然掛了。
這題的n,m範圍是1000。這個網格圖有n*m個點,有n*(m - 1) + m * (n - 1) + (n - 1) * (m - 1)條邊。再加上網路流的複雜度O(n^2m),如果直接建圖硬跑的話,複雜度最壞會達到O(n^3m^3),顯然無法接受。
正確做法是先求給定網格圖的對偶圖,然後在對偶圖上跑最大流,將最小割問題轉化為最短路問題。好巧妙。具體可以戳這裡。這篇論文講的很詳細。
具體做法是:
在給定的s-t平面圖中,s到t連一條邊,然後對這張圖建對偶圖。對偶圖也比較好建:對於原圖的每個面抽象出一個點,對於原圖的每一條邊,這條邊相鄰的2個面的點連邊,邊權為這條原圖中邊的權值。對原圖的s-t加了 一條邊,那麼就相應的多了一個面,這個面抽象出新的點s',最外面的無介面抽象出點t',這樣建好對偶圖後我們會發現,對偶圖中從s'到t'的任何一條路徑都會將原圖中的s和t分成2部分。即對偶圖中s'到t'的任何一條路徑都是原圖的一個割,那麼在對偶圖中找的那條最短路便是原圖的最小割!這個真是太巧妙了!!
再分析一下這種做法的複雜度:
首先對於改造後的原圖:
點的數量:n*m
面的數量:2*(n - 1) * (m - 1) + 2
邊的數量:n*(m - 1) + m * (n - 1) + (n - 1) * (m - 1) + 1
是滿足尤拉定理的。
那麼相應的對偶圖:
點的數量:2*(n - 1) * (m - 1) + 2 = O(n*m)
面的數量:n*m
邊的數量:n*(m - 1) + m * (n - 1) + (n - 1) * (m - 1) + 1 = 3*m*n - 2*(m+n) + 2
那麼新圖中跑最短路的話,如果用裸dijkstra,時間複雜度就降到了O(n^2m^2),還是比較大,不過已經優化很明顯了。
spfa的話複雜度O(km*n),k為常數。
如果是dijkstra+heap的話,複雜度O(n*m*log(n*m)),已經很優秀了。
我是直接跑的spfa:)
好像很慢的樣子,改天試試dijkstra+heap吧。。。
詳情請見程式碼:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1003;
const int M = 2000005;
const int inf = 0x3f3f3f3f;
int head[M];
struct node
{
int to,next,val;
}g[M + M];
int m,n,nm,num;
int dis[M],que[M];
bool flag[M];
void add(int s,int e,int v)
{
g[num].to = e;
g[num].val = v;
g[num].next = head[s];
head[s] = num ++;
}
int nextint()
{
int ret;
char c;
while((c = getchar()) > '9' || c < '0')
;
ret = c - '0';
while((c = getchar()) >= '0' && c <= '9')
ret = ret * 10 + c - '0';
return ret;
}
void build()
{
scanf("%d",&m);
nm = (n * m - m - n + 1)<<1;//there are nm nodes in new graph except s and t;
int i,j,tmp;
memset(head,-1,sizeof(head));
num = 0;
for(j = 1;j < m;j ++)//
{
scanf("%d",&tmp);
add(j,nm + 1,tmp);
}
for(i = 1;i < n - 1;i ++)
{
for(j = 1;j < m;j ++)
{
scanf("%d",&tmp);
add((i<<1)* (m - 1) + j,((i<<1) - 1) * (m - 1) + j,tmp);
}
}
for(j = 1;j < m;j ++)
{
scanf("%d",&tmp);
add(0,((n<<1)-3) * (m - 1) + j,tmp);
}
for(i = 0;i < n - 1;i ++)
{
for(j = 1;j <= m;j ++)
{
scanf("%d",&tmp);
if(j == 1)
add(0,(i<<1)*(m - 1) + m,tmp);
else if(j == m)
add((i<<1|1)*(m - 1),nm + 1,tmp);
else
add((i<<1)*(m - 1) + j - 1,(i<<1)*(m - 1) + j + m - 1,tmp);
}
}
for(i = 0;i < n - 1;i ++)
{
for(j = 1;j < m;j ++)
{
scanf("%d",&tmp);
add((i<<1|1)*(m - 1) + j,(i<<1)*(m - 1) + j,tmp);
}
}
}
void SPFA()
{
int i,front,rear;
memset(flag,false,sizeof(flag));
memset(dis,0x3f,sizeof(dis));
front = rear = dis[0] = 0;
que[rear ++] = 0;
flag[0] = true;
while(front != rear)
{
int u = que[front ++];
flag[u] = false;
if(front == M)
front = 0;
for(int i = head[u];~i;i = g[i].next)
{
int v = g[i].to;
if(dis[v] > dis[u] + g[i].val)
{
dis[v] = dis[u] + g[i].val;
if(flag[v] == false)
{
flag[v] = true;
que[rear ++] = v;
if(rear == M)
rear = 0;
}
}
}
}
}
void solve()
{
SPFA();
printf("%d\n",dis[nm + 1]);
}
int main()
{
while(scanf("%d",&n) != EOF)
{
build();
solve();
}
return 0;
}
//73072 kb 3688 ms