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