【網絡流】【二分圖最大匹配】Buaacoding1043 難題·Beihang Couple Pairing Comunity 2017

難題·Beihang Couple Pairing Comunity 2017
時間限制: 2000 ms 內存限制: 131072 kb
總通過人數: 10 總提交人數: 15
題目描述
BCPC(Beihang Couple Pairing Community)2017 就要舉辦了!

這是 Beihang U 一年一度的盛會,在這樣的日子裏,每一隻單身狗們都可以聚集在一起自由地配對, 每一對成功配對的單身狗就可以順利地進入到最後的禮堂中,接受所有與會單身狗們的祝福,並從脫離狗身,羽化登仙。

ConnorZhong 拉上可愛的三位助教,AlvinZH ,Bamboo,ModricWang 也參與到了這項集會中,可是無一例外,他們都沒能成功配對。

最後四個人都被攔在了禮堂外,看着禮堂內哲學的畫面,再看看空蕩蕩的周圍,竟然只有這四個人被攔在了外面,這意味着什麼?意味着等今晚生米煮成熟飯,這個學校很可能只剩這四隻單身狗了啊!

絕望的四個人在寒風中瑟瑟發抖,沙河的風真是喧囂啊。

”我們不能眼看着這可怕的事情發生!“, AlvinZH,退學大隊隊長說道。

“那真是天可怕了!”,ConnorZhong應道。

大家一拍即合。ConnorZhong 找來火把,ModricWang 掏出了汽油,AlvinZH 點火,Bamboo 在一邊加油…

當夜大禮堂火光沖天 ….

一個禮堂平面圖以一個 n×m 的矩陣給出,矩陣中 ‘X’ 爲承重牆,不可通行,’.’ 爲空地,可以自由通行,E 爲緊急疏散傳送門出口。在火災發生的時候,每個 ‘.’ 中都恰好有一對單身狗(2 只!)正在哲學,禮堂中的所有單身狗都嘗試從 E 疏散,但是緊急疏散通道的疏散能力有限,每個出口每一秒鐘都只能有一對單身狗(2只!)同時疏散。單身狗只能在 . 上通行,且每一秒只能上下左右四個方向移動一個單位,每一個 . 上同一時間可以有多隻狗。

注意,地圖上只有 ‘.’ 可以任由狗自由通行和堆積,‘E’ 和 ‘X’ 都不可以自由通行和堆積,但是 ‘E’ 每一秒可以由相鄰節點的任一對單身狗進入並疏散。

請問最少需要多少秒,禮堂中的單身狗能全部從禮堂中疏散。

如果有單身狗不能從禮堂中撤離,那麼請輸出一行:“Oh, poor single dog!”.

輸入
第一個數爲數據組數 T.

每組測試數據第一行爲兩個正整數 n 和 m ,表示禮堂的大小。

接下來 n 行,每行 m 個字符,表示禮堂的構造。sij(1≤i≤n,1≤j≤m) 爲’X’,’.’ 或 E。

T≤20,1≤n≤20,1≤m≤20

輸出
對於每組數據,輸出一行,如果單身狗們能全部撤離,輸出最短撤離時間,否則輸出一行"Oh, poor single dog!" (不包含左右引號)。

輸入樣例
3
3 3
XEX

XEX
3 7
…E
.XXXXXX

3 3
.X.
XEX

輸出樣例
2
14
Oh, poor single dog!
HINT
有問題歡迎尋找ConnorZhong實錘!


 真的不敢說自己是搞ACM的了,軟院的題都狂WA狂T不止,之前有一道dp還看了題解抄了代碼…說到底還是自己菜到不行。
 這題剛看的時候立馬反應過來是網絡流。瞬間想到了之前做過的一道題,是白書上的LA2957 運送超級計算機。然後就依樣畫葫蘆建立分層圖。從小到大枚舉時間T,然後T增加過程中增加一層圖,再在原有基礎上增廣,一直到流量達到人數爲止。其實思路沒啥大問題,但是每一次增加400個節點,然後上一層每個‘.’節點要對新一層相鄰四個點和本身連邊,瞬間點數邊數爆炸。結果是TLE,像20*20,右下角一個E的數據,大概二十多秒纔出的來。
 後來就開始想能不能忽略時間因素,不建立分層圖,只用400個點,然後每一次給 '.‘與‘E’間距離==當前時間 的點對連接新邊。小數據發現基本問題,但是實際上還是會有兩個’.'在同一時刻到達E,出現阻塞的情況,所以必須考慮時間。
 一些典型數據
1
4 4
E . E .
X E. .
X E . .
. E . .
答案 3
1
3 5
. . . X E
. . . . .
. E E . E
答案 4

 後來反思了一下,認爲建立分層圖的方法中,每個點向下一層連的邊是+∞的流量,一般來說這種邊很可能是冗餘的。上面的方法雖然因爲沒有分層而錯誤,但直接爲’.'和‘E’建邊確實是更好的選擇。另外,這時可以發現,‘.’其實是可以不用分層的,只需要爲終點’E’分層就行了,這樣的話,點數和邊數就大大減少了。貼一下代碼

#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
#include<queue>
#define INF 0x7FFFFFFF
#define maxn 160005
using namespace std;

const int dx[5]={0,1,0,-1,0};
const int dy[5]={1,0,-1,0,0};

struct edge
{
	int to,residual;
};

char f[25][25];
bool vis[405][25][25];
queue<pair<int,int>> Q[405];
int n,m,k,s,t,nn,mm,flow,T,EN,ans,tag[25][25];
vector<edge> E;
vector<int> G[maxn];
bool flag;
int d[maxn],num[maxn],cur[maxn];

int DFS(int x, int a, int fa)
{
	if(x==t||a==0)
		return a;
	int flows=0,f;
	for(int& i=cur[x];i<G[x].size();i++)	
	{
		edge& e=E[G[x][i]];
		if(d[x]==d[e.to]+1&&(f=DFS(e.to,min(a,e.residual),x)))
		{
			flows+=f;
			if(!flag)
				return flows;
			e.residual-=f;
			E[G[x][i]^1].residual+=f;
			a-=f;
			if(a==0)
				return flows;
		}
	}
	int m=n-1;
	cur[x]=0;
	for(int i=0;i<G[x].size();i++)
		if(E[G[x][i]].residual||E[G[x][i]].to==fa&&flows>0)
		    m=min(m,d[E[G[x][i]].to]);
	if(--num[d[x]]==0)
		flag=false;
	num[d[x]=m+1]++;
	return flows;
}

int ISAP_Maxflow()
{
	for(int i=1;i<=n;i++)
		d[i]=num[i]=cur[i]=0;
	num[0]=n;
	int x=s;
	flag=true;
	while(d[s]<n&&flag)
		flow+=DFS(s,INF,-1);
	return flow;
}

void add_edge(int p, int q, int cap)
{
	E.push_back((edge){q,cap});
	E.push_back((edge){p,0});
	G[p].push_back(E.size()-2);
	G[q].push_back(E.size()-1);
}

void init()
{
	flow=0;
	for(int i=1;i<=EN;i++)
	{
		while(!Q[i].empty())
			Q[i].pop();
	}
	E.clear();
	for(int i=1;i<=n;i++)
		G[i].clear();
	n=2;
	EN=0;
	for(int i=1;i<=nn;i++)
		for(int j=1;j<=mm;j++)
			if(f[i][j]=='E')
			{
				++EN;
				while(!Q[EN].empty())
					Q[EN].pop();
				for(int p=1;p<=nn;p++)
					for(int q=1;q<=mm;q++)
						vis[EN][p][q]=false;
				Q[EN].push({i,j}),++n;   //每次隊列擴展出新E要連接的點
			}
	for(int i=1;i<=nn;i++)
		for(int j=1;j<=mm;j++)
			if(f[i][j]=='.')
			{
				add_edge(s,++n,1);  //超級源點到'.'
				tag[i][j]=n;
			}
}

void expand()
{		
	for(int i=1,tp;i<=EN;i++)
	{
		for(int p=1;p<=nn;p++)
			for(int q=1;q<=mm;q++)
				vis[i][p][q]=false;
		add_edge(tp=++n,t,1);
		queue<pair<int,int>> tmp;
		while(!Q[i].empty())
		{
			pair<int,int> R=Q[i].front();
			Q[i].pop();
			for(int k=0,u,v;k<=4;k++)
			{
				u=R.first+dx[k],v=R.second+dy[k];
				if(u>0&&u<=nn&&v>0&&v<=mm&&f[u][v]=='.'&&!vis[i][u][v])
				{
					tmp.push({u,v}),vis[i][u][v]=true;  //新一層的'E'到超級匯點
					add_edge(tag[u][v],tp,1);  //'.'到此時新的'E'
				}
			}
		}
		swap(Q[i],tmp);
	}
}

int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&nn,&mm);
		int sdog=0;
		for(int i=1;i<=nn;i++)
		{
			scanf("%s",f[i]+1);
			for(int j=1;j<=mm;j++)
				if(f[i][j]=='.')
					sdog+=1;
		}
		ans=1;
		flow=0;
		s=1,t=2;
		init();
		expand();
		for(int lflow=flow;ISAP_Maxflow()<sdog&&flow>lflow;ans++)  //不斷增廣
		{
			expand();
			lflow=flow;
		}
		if(flow>=sdog)
			printf("%d\n",ans);
		else
			puts("Oh, poor single dog!");
	}
	return 0;
}

 因爲不可能存在某個時刻沒有單身狗到達出口(這樣顯然不是最優的,每個出口都不接受單身狗,說明此時每隊單身狗和他要離開的出口都不相鄰,那麼肯定有方法讓單身狗跟隨與之相鄰的,上一步離開的單身狗離開,至少會少一對單身狗),因此某個時刻如果流量沒有增長,那麼一定是被隔離的情況,直接輸出無解,不用用bfs再判斷圖的連通性了。
 其實大家可以發現這個圖已經變成一個二分圖了,所以沒必要用網絡流了,直接來一個二分圖最大匹配,直接把所有點和邊建好,然後按順序增廣,增廣到匹配數量達到要求了,看枚舉到了哪一天的點,那麼答案就是哪一天。
 二分圖到底更快速一些…效率無敵。
 其實直接想到用二分圖做也不是不可能。可惜思維有點定勢了,導致這題思考的時候拐了一個大彎。
 附上mogg的二分圖代碼,雖然貼別人的代碼有點不太道德的樣子QwQ

#include <cstdio>
#include <algorithm>
#include <climits>
#include <vector>
#include <queue>
#include <iostream>
#include <string>
#include <map>
#include <unordered_map>
#include <cstdlib>
#include <cstring>
using namespace std;

string mp[25];

struct Point
{
    int x, y;
};
bool operator == (const Point&l, const Point&r) { return l.x == r.x && l.y == r.y; }
bool operator < (const Point&l, const Point&r) { return l.x == r.x ? l.y < r.y : l.x < r.x; }
vector< vector<vector<int>>> dis;

vector<Point> p, e;
int n, m, k;
const int ms = 666666;
vector<int> G[ms];//鄰接矩陣
int match[ms];//記錄匹配點
bool visit[ms];//記錄是否訪問
int ps, os;

bool dfs(int x)//尋找增廣路徑
{
    int len = G[x].size();
    for (int i = 0; i < len; ++i)
    {
        int to = G[x][i];
        if (!visit[to])
        {
            visit[to] = true;
            if (match[to] == -1 || dfs(match[to]))
            {
                match[to] = x;
                return true;
            }
        }
    }
    return false;
}

int MaxMatch()
{
    int ans = 0;
    memset(match, -1, sizeof(match));
    for (int i = 0; i <= n; ++i)
    {
        memset(visit, false, sizeof(visit));//清空訪問
        if (dfs(i))
        {
            ans++;
            if (ans == ps)
                return i / os + 1;
        }
    }
    return -1;
}

int dir[] = { -1,0,1,0 };
void bfs(const Point& x, int index)
{
    dis[index][x.x][x.y] = 0;
    queue<Point> q;
    q.push(x);
    int res = 0;
    while (!q.empty())
    {
        Point p1 = q.front();
        q.pop();
        for (int i = 0; i < 4; i++)
        {
            Point p2 = { p1.x + dir[i],p1.y + dir[3 - i] };
            if (p2.x < n&&p2.x >= 0 && p2.y < m&&p2.y >= 0 && dis[index][p2.x][p2.y] < 0 && mp[p2.x][p2.y] == '.')
            {
                dis[index][p2.x][p2.y] = dis[index][p1.x][p1.y] + 1;
                q.push(p2);
            }
        }
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    cin >> t;
    while (t--)
    {
        cin >> n >> m;
        for (int i = 0; i < n; i++)
            cin >> mp[i];
        vector<Point> ot, per;
        for (int i = 0; i < n; i++)
            for (int j = 0; j < m; j++)
                if (mp[i][j] == '.')
                    per.push_back({ i,j });
                else if (mp[i][j] == 'E')
                    ot.push_back({ i,j });
        int maxx = n * m;
        ps = per.size();
        os = ot.size();
        dis.clear();
        dis = vector<vector<vector<int>>>(os, vector<vector<int>>(n, vector<int>(m, -1)));
        for (int j = 0; j < os; j++)
            bfs(ot[j], j);
        for (int i = 0; i < ps; i++)
            for (int j = 0; j < os; j++)
            {
                int d = dis[j][per[i].x][per[i].y];
                if (d >= 0)
                    for (int k = d; k <= maxx; k++)
                        G[(k - 1)*os + j].push_back(maxx*os + i);
            }
        n = maxx * os + ps;
        int res = MaxMatch();
        if (res == -1)
            printf("Oh, poor single dog!\n");
        else
            printf("%d\n", res);
        for (int i = 0; i < n; i++)
            G[i].clear();
    }
    return 0;
}

 最後總結一下,自己寫代碼不能總帶着思維定勢,也不能瞎搞,要冷靜分析。
 網絡流建圖很重要,好的建圖效果會大大提升。
 加強練習,不可以有半點鬆懈!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章