P2055 [ZJOI2009]假期的宿舍

首先先理清楚一下这题的关系,每一个人要么是学校里的学生,要么不是。而是学校里学生,要么回家了,要么没回家。那么可以提供床位的,就是学校的在校学生,而需要床位的,就是不是这个学校的学生的人,和在学校没回家的在校学生。一个人占一张床,而在校学生自己可以睡自己的床,因此我们把所有的人分成两部分,需要床的和提供床的,在校学生不回家的两边都有(建一条自己到自己的边),根据认识关系建边,因此我们得到了二分图,而我们所需要的睡尽可能多的人,因此就是求二分图的最大匹配

因此大概有两个算法可以求解

  1. 匈牙利算法求最大匹配
    最大匹配算法,如果当前的点可以只通过一条边找到匹配点,那么就直接把这个点匹配上,如果当前点连接的所有点都被匹配过了,那么我们就继续dfs寻找之前连接过的点能不能找到新的点来连接,然后腾出一个未被匹配的和当前点连接的点来和当前点匹配。
    每个点都返回是否能匹配上,记录匹配数,输出即可
    代码如下
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100;
int school[maxn];
int home[maxn];
int head[maxn];
int vis[maxn],match[maxn];
int tot,n;
struct _edge {int to,nex; }edge[maxn*maxn];
void add(int u,int v){
    edge[tot] = (_edge){v,head[u]};
    head[u] = tot++;
}
bool dfs(int u){
    for (int i=head[u]; ~i; i=edge[i].nex) {
        int v = edge[i].to;
        if (!vis[v]){
            vis[v] = 1;
            if (!match[v] || dfs(match[v])){
                match[v] = u;
                return true;
            }
        }
    }
    return false;
}
void init(){
    tot = 0;
    memset(head, -1, sizeof(head));
    memset(match, 0, sizeof(match));
}
int main(){
   // freopen("/Users/chutong/data.txt", "r", stdin);
    int T,rel;
    scanf("%d",&T);
    while (T--) {
        scanf("%d",&n); tot = 0; init();
        for (int i=1; i<=n; i++)
            scanf("%d",&school[i]);
        for (int i=1; i<=n; i++) {
            scanf("%d",&home[i]);
            if (!home[i] && school[i]) add(i, i);
        }
        for (int i=1; i<=n; i++) {
            for (int j=1; j<=n; j++) {
                scanf("%d",&rel);
                if (rel && school[j]){
                    add(i, j);
                }
            }
        }
        int sum = 0;
        for (int i=1; i<=n; i++) {
            if ((school[i] && !home[i]) || !school[i]){
                memset(vis, 0, sizeof(vis));
                if (dfs(i)) sum--;
                sum++ ;
            }
        }
        if (sum)  cout<<"T_T"<<endl;
        else cout<<"^_^"<<endl;
    }
    return 0;
}


  1. 网络流最大流
    建立一个超级原点s连接所有提供床位的点和一个超级汇点t连接所有需要床位的点,我们将每条边的权记录为1,那么跑网络流算法,求出最大流量即可。
    代码如下
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6;
struct _edge { int to, nex, c, f;  } edge[maxn];
int head[maxn],dep[maxn];
int n,m,s,t,tot;
int bfs() {
    memset(dep, 0, sizeof(dep));
    queue<int> q; q.push(s);
    dep[s] = 1;
    while(!q.empty()) {
        int u = q.front(); q.pop();
        for(int i = head[u]; ~i; i = edge[i].nex) {
            int v = edge[i].to;
            if(!dep[v] && edge[i].c > edge[i].f) {
                dep[v] = dep[u] + 1; q.push(v);
            }
        }
    }
    if (dep[t] != 0) return 1;
    else return 0;
}
int dfs(int u, int capflow) {
    if (u == t) return capflow;
    int addflow = 0;
    for(int i = head[u]; i != -1 && addflow < capflow; i = edge[i].nex){
        int v = edge[i].to;
        if(dep[u]+1 == dep[v] && edge[i].c > edge[i].f) {
            int nowflow = dfs(v, min(capflow-addflow, edge[i].c-edge[i].f));
            edge[i].f += nowflow;
            edge[i^1].f -= nowflow;
            addflow += nowflow;
        }
    }
    return addflow;
}
int dinic() {
    int maxflow = 0;
    while(bfs()) maxflow += dfs(s, INT_MAX);
    return maxflow;
}
void add(int u, int v, int c) {
    edge[tot] = (_edge){v,head[u],c,0};
    head[u] = tot++;
    edge[tot] = (_edge){u,head[v],0,0};
    head[v] = tot++;
}
void init(){
    memset(dep, 0, sizeof(dep));
    memset(edge, 0, sizeof(edge));
    memset(head, -1, sizeof(head));
    tot = 0;
}
int school[maxn],home[maxn];
int rel[105][105];
int main() {
    // freopen("/Users/chutong/data.txt", "r", stdin);
    int T;
    scanf("%d",&T);
    while (T--) {
        init();
        scanf("%d",&n);
        s = 0; t = n*2 + 1;
        for (int i=1; i<=n; i++){
            scanf("%d",&school[i]);
            if (school[i]) add(i+n, t, 1);
        }
        int res = 0;
        for (int i=1; i<=n; i++){
            scanf("%d",&home[i]);
            if ((school[i] && !home[i]) || !school[i]){
                add(s, i, 1); res++;
            }
        }
        for (int i=1; i<=n; i++){
            for (int j=1; j<=n; j++){
                scanf("%d",&rel[i][j]);
                if (rel[i][j] || i == j) {
                    add(i, j+n, 1);
                }
            }
        }
        int ans = dinic();
        if (ans >= res) printf("^_^\n");
        else printf("T_T\n");
    }
    return 0;
}



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值