【网络流】【二分图最大匹配】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;
}

 最后总结一下,自己写代码不能总带着思维定势,也不能瞎搞,要冷静分析。
 网络流建图很重要,好的建图效果会大大提升。
 加强练习,不可以有半点松懈!

猜你喜欢

转载自blog.csdn.net/BUAA_Alchemist/article/details/85119443