洛谷 P2055 [ZJOI2009]假期的宿舍

时间:2023-03-09 18:31:00
洛谷 P2055 [ZJOI2009]假期的宿舍

洛谷 P2055

题目描述

学校放假了 · · · · · · 有些同学回家了,而有些同学则有以前的好朋友来探访,那么住宿就是一个问题。比如 A 和 B 都是学校的学生,A 要回家,而 C 来看B,C 与 A 不认识。我们假设每个人只能睡和自己直接认识的人的床。那么一个解决方案就是 B 睡 A 的床而 C 睡 B 的床。而实际情况可能非常复杂,有的人可能认识好多在校学生,在校学生之间也不一定都互相认识。我们已知一共有 n 个人,并且知道其中每个人是不是本校学生,也知道每个本校学生是否回家。问是否存在一个方案使得所有不回家的本校学生和来看他们的其他人都有地方住。

输入输出格式

输入格式:

第一行一个数 T 表示数据组数。接下来 T 组数据,每组数据第一行一个数n 表示涉及到的总人数。接下来一行 n 个数,第 i
个数表示第 i 个人是否是在校学生 (0 表示不是,1 表示是)。再接下来一行 n 个数,第 i 个数表示第 i 个人是否回家 (0
表示不会家,1 表示回家,注意如果第 i 个人不是在校学生,那么这个位置上的数是一个随机的数,你应该在读入以后忽略它)。接下来 n 行每行 n
个数,第 i 行第 j 个数表示 i 和 j 是否认识 (1 表示认识,0 表示不认识,第 i 行 i 个的值为
0,但是显然自己还是可以睡自己的床),认识的关系是相互的。

输出格式:

对于每组数据,如果存在一个方案则输出 “ ^_^ ”(不含引号) 否则输出“T_T”(不含引号)。(注意输出的都是半角字符,即三个符号的 ASCII 码分别为94,84,95)

输入输出样例

输入样例#1:
1
3
1 1 0
0 1 0
0 1 1
1 0 0
1 0 0
输出样例#1:
^_^

说明

对于 30% 的数据满足 1 ≤ n ≤ 12。

对于 100% 的数据满足 1 ≤ n ≤ 50,1 ≤ T ≤ 20。

多组数据!!!

Solution:

这道题一看就类似于二分图匹配的问题,既可以用匈牙利算法来做也可以用最大流做。

1、用匈牙利算法。思路就是把每个是学校里的学生和他们自己的床位相连且边权为1(实际实现时可以都连,只需要在增广时判断就行了),再统计需要在学校住的同学的人数(因为只有这些人需要床位),然后把相互认识的人和床位相连且边权为1(实际实现时这里也可以不必判断是否是学校的学生,只需要增广时特判就行了)。。。关键的增广时,记得判断:必须是留在学校的学生或者外来的学生,才能以他为起点进行增广。最后若增广累加的值等于一开始统计的需要在学校住的人数,则可以,否则不行。

代码:

 #include<bits/stdc++.h>
#define il inline
using namespace std;
const int N=,inf=;
int T,n,l[N],k[N],cnt,pre[N],ans,tot,h[N];
bool vis[N];
struct edge{
int to,net;
}e[N*];
il void add(int u,int v)
{
e[++cnt].to=v,e[cnt].net=h[u],h[u]=cnt;
}
il bool find(int u)
{
for(int i=h[u];i;i=e[i].net){
int v=e[i].to;
if(!vis[v]){
vis[v]=;
if(!pre[v]||find(pre[v])){pre[v]=u;return ;}
}
}
return ;
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
ans=tot=;cnt=;
memset(h,,sizeof(h));
memset(pre,,sizeof(pre));
for(int i=;i<=n;i++)scanf("%d",&l[i]);
for(int i=;i<=n;i++){
scanf("%d",&k[i]);
if(l[i]&&!k[i])add(i,i+n);
}
for(int i=;i<=n;i++)
if(!l[i]||(l[i]&&!k[i]))tot++;
int x;
for(int i=;i<=n;i++)
for(int j=;j<=n;j++){
scanf("%d",&x);
if(x&&l[j])add(i,j+n);
}
for(int i=;i<=n;i++)
if(!l[i]||(l[i]&&!k[i])){
memset(vis,,sizeof(vis));
if(find(i))ans++;}
// printf("%d %d\n",ans,tot);
if(ans==tot)printf("^_^\n");
else printf("T_T\n");
}
return ;
}

2、用最大流做。思路与匈牙利算法的类似,但是附加源点S(连向需要住校的人并赋权值为1)和汇点T(由每个床位连向T并赋权值为1),然后与匈牙利算法相同,统计需要住校的人数,加边时注意必须是需要住校的且认识的才能与床位相连(该回家的肯定是回家好,这样不占床位),最后增广累加答案,若等于需要住校的人数则可以,否则不行。

代码:

 #include<bits/stdc++.h>
#define il inline
using namespace std;
const int N=,inf=;
int T,n,s,t=,l[N],k[N],cnt=,dis[N],h[N],ans,tot;
queue<int>q;
struct edge{
int to,net,v;
}e[N*];
il void add(int u,int v,int w)
{
e[++cnt].to=v,e[cnt].v=w,e[cnt].net=h[u],h[u]=cnt;
e[++cnt].to=u,e[cnt].v=,e[cnt].net=h[v],h[v]=cnt;
}
il bool bfs()
{
memset(dis,-,sizeof(dis));
q.push(s),dis[s]=;
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=h[u];i;i=e[i].net)
if(dis[e[i].to]==-&&e[i].v>)dis[e[i].to]=dis[u]+,q.push(e[i].to);
}
return dis[t]!=-;
}
il int dfs(int u,int op)
{
if(u==t)return op;
int flow=,used=;
for(int i=h[u];i;i=e[i].net)
{
int v=e[i].to;
if(dis[v]==dis[u]+&&e[i].v>)
{
used=dfs(v,min(op,e[i].v));
if(!used)continue;
flow+=used,op-=used;
e[i].v-=used,e[i^].v+=used;
if(!op)break;
}
}
if(!flow)dis[u]==-;
return flow;
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=;i<=N;i++)h[i]=,l[i]=,k[i]=;
//memset(e,0,sizeof(e));
cnt=;ans=tot=;
int x;
for(int i=;i<=n;i++)scanf("%d",&l[i]);
for(int i=;i<=n;i++){
scanf("%d",&x);
if(l[i]==)k[i]=x;
}
for(int i=;i<=n;i++){
if(!k[i])add(s,i,),tot++;
if(l[i])add(i+n,t,);
}
for(int i=;i<=n;i++)
for(int j=;j<=n;j++){
scanf("%d",&x);
if(x==||i==j)add(i,j+n,);
}
while(bfs())ans+=dfs(s,inf);
if(ans==tot)printf("^_^\n");
else printf("T_T\n");
}
return ;
}