pku1904——二分图/增广链/强连通分量

首先赞一下这道题,的确是个好题。

题目大意:一个国王有n个王子,同时有n个女孩。每个王子都有自己喜欢的若干个女孩,现给定一个合法的完备匹配(也就是一个王子娶其中一个自己喜欢女孩),求每个王子可以选择哪些女孩可以让剩下的每个王子依旧能够选择到自己喜欢的一个女孩。

题目有点绕。。但确实是这样的意思。。。。

解法:

首先我们看最直观的想法——也就是枚举+二分匹配判定。但是点数n高达2000,边数e高达200000;所以枚举的复杂度O(e+n)*O(n^2)=O(n^2 * (e+n)),太大了,显然超时。

但是有没有可以优化的地方呢?有的。因为题目已经给定了一个完备匹配,而上述算法还没有利用这个完备匹配。想象一下,我们已经有一个完备匹配了。假如王子xi对应女孩yi是完备匹配中的一条边,那么yi肯定是必定属于可选集合的。对于不是yi的yj,如果王子喜欢yj,那么假设我们现在让王子选择yj,由于必须形成一个完备匹配,如果match[yj]==xj  (match[yj]保存的是给定的完备匹配中与女孩yj匹配的王子xj),那么只要能从xj出发找到一条新的增广路径,则我们的假设可以成立。 这个优化的本质还是在进行匹配,只不过我们没有重新进行新的匹配,而是充分利用已给出的匹配,仅仅试探性的寻找,看看是否存在可行的增广路。

这个算法的时间复杂度是:O((e+n)*n),已经优化掉了1维,但是在单case2000ms的限制下还是会挂掉的。

我们必须继续优化,或者换新的思路。

可以发现,我们刚才虽然优化了,但是算法依旧做了一些无用功,对于每次枚举,我们只希望知道是否存在新的增广路径,而上面的优化显然已经找出了可行增广链。我们如何才能不通过寻找增广链而直接确定是否存在增广链呢?可以的。让我们再次回到初始的想法,如果给定的完备匹配中xi->yi,现在我们想让xi指向yj。如果让xi->yj的话,那么yi显然对于xi已经没用了,又因为最终的匹配必然是一个完备匹配,所以假如寻找到了增广链,那么增广链的最后一条边所指向的节点必然是yi。假如我们由yi向xi再连一条边会发生什么呢? 很明显,形成了一个环!也就是说,形成了一个强连通分量。所以说,如果xi可以选择yj,那么xi和yj必然属于同一个强连通分量。所以此题可用强连通分量来求解。

具体解法:对每个王子向他喜欢的女孩连一条边,对于给定的完备匹配,由女孩向对应的男孩连一条匹配边。然后求这个二分图的强连通分量,如果某个xi和他喜欢的某个yj属于同一个强连通分量,那么yj对于xi是可选的。

算法复杂度:2*O(e+n) = O(e+n),对于单case完全可以满足。

注意这个题的输入数据量很大,使用scanf和printf都要10000+ms。。。

枚举+增广链TLE代码:

View Code
#include <iostream>
#include
<cstdio>
#include
<cstring>
#include
<vector>
#include
<algorithm>
using namespace std;

const int N = 2010;
const int M = 205000;

int n, tot, h[N], nxt[M], v[M], match[N], back[N];
bool visit[N];

vector
<int> ans;

void add(int a, int b)
{
v[tot]
= b;
nxt[tot]
= h[a];
h[a]
= tot++;
}

bool find(int u)
{
int p;
for(p = h[u]; p != -1; p = nxt[p])
{
if(!visit[v[p]])
{
visit[v[p]]
= true;
if(match[v[p]]==-1 || find(match[v[p]]))
return true;
}
}
return false;
}

void solve()
{
int i, j, len;
for(i = 1; i <= n; i++)
{
ans.clear();
for(j = h[i]; j != -1; j = nxt[j])
{
if(match[v[j]] == i)
{
ans.push_back(v[j]);
continue;
}
else
{
memset(visit,
false, sizeof(visit));
match[back[i]]
= -1;
visit[v[j]]
= true;
if(find(match[v[j]])) ans.push_back(v[j]);
match[back[i]]
= i;
}
}
sort(ans.begin(), ans.end());
len
= ans.size();
printf(
"%d", len);
for(j = 0; j < len; j++) printf(" %d", ans[j]);
printf(
"\n");
}
}

int main()
{
int i, k, x;
scanf(
"%d", &n);
memset(h,
-1, sizeof(h));
tot
= 0;
for(i = 1; i <= n; i++)
{
scanf(
"%d", &k);
while(k--)
{
scanf(
"%d", &x);
add(i, x);
}
}
for(i = 1; i <= n; i++)
{
scanf(
"%d", &x);
match[x]
= i;
back[i]
= x;
}
solve();
return 0;
}

强连通分量AC代码:

View Code
#include <iostream>
#include
<cstdio>
#include
<cstring>
#include
<vector>
using namespace std;

const int N = 2010;
const int M = 205000;

int n, tot, depth, top, ct, h[N+N], low[N+N], d[N+N], f[N+N], stack[N+N], nxt[M], v[M];
bool instack[N+N], map[N][N];

vector
<int> ans;

void add(int a, int b)
{
v[tot]
= b;
nxt[tot]
= h[a];
h[a]
= tot++;
}

void dfs(int u, int &depth)
{
int p, x;
d[u]
= low[u] = depth++;
stack[
++top] = u;
instack[u]
= true;
for(p = h[u]; p != -1; p = nxt[p])
{
if(d[v[p]] == -1)
{
dfs(v[p], depth);
low[u]
= min(low[u], low[v[p]]);
}
else if(instack[v[p]])
low[u]
= min(low[u], d[v[p]]);
}
if(low[u] == d[u])
{
while(top)
{
x
= stack[top--];
instack[x]
= false;
f[x]
= ct;
if(x == u) break;
}
ct
++;
}
}

int main()
{
int i, j, k, x, len;
memset(map,
false, sizeof(map));
scanf(
"%d", &n);
tot
= 0;
memset(h,
-1, sizeof(h));
for(i = 1; i <= n; i++)
{
scanf(
"%d", &k);
while(k--)
{
scanf(
"%d", &x);
map[i][x]
= true;
add(i, x
+n);
}
}
for(i = 1; i <= n; i++)
{
scanf(
"%d", &x);
add(x
+n, i);
}
memset(d,
-1, sizeof(d));
memset(f,
-1, sizeof(f));
depth
= top = ct = 0;
memset(instack,
0, sizeof(instack));
for(i = 1; i <= n; i++)
if(d[i] == -1)
dfs(i, depth);
for(i = 1; i <= n; i++)
{
ans.clear();
for(j = n+1; j <= (n<<1); j++)
if(map[i][j-n] && f[j]==f[i])
ans.push_back(j
-n);
len
= ans.size();
printf(
"%d", len);
for(j = 0; j < len; j++) printf(" %d", ans[j]);
printf(
"\n");
}
return 0;
}

你可能感兴趣的:(二分图)