M - Escape HDU - 3605 状态压缩+最大流

题意:世界末日,地球上有n个人,现有适合人类居住的有m个星球,每个人都有自己想要去的星球,问在满足每个人的需求的情况下能不能把所有的人全部救走。
算法:


TLE思路一:
根据题意和直觉来讲,确实是最大流,超级源点到每个人连线,再从每个人到他想要的星球连线,然后这样下来最复杂就会有1e6条边,dinic跑下来肯定会T,然后就去搜了题解有了第二种解法
TLE代码:

#include <bits/stdc++.h>
using namespace std;
#define met(a, b) memset(a, b, sizeof(a))
const int inf = 0x7fffffff;
const int maxn = 1000000 + 5;
struct EDGE {
    int to, vol, next;
} edges[1000000];
int n, m, allPeople[50], edgesNum = 0, Head[maxn];
int dep[maxn], cur[maxn];
int S, T;
void buildGraph();
void addEdge(int u, int v, int w);
bool bfs(int s, int t);
int dfs(int u, int flow);
int dinic(int s, int t);
int main() {
    while(~scanf("%d%d", &n ,&m)) {
        S = 0;
        T = n + m + 1;
        edgesNum = 0;
        met(Head, -1);
        int d;
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= m; j++) {
                scanf("%d", &d);
                if(d == 1) addEdge(i, n + j, 1);
            }
        }
        for(int i = 1; i <= m; i++) {
            scanf("%d", &allPeople[i]);
        }
        buildGraph();
        int ans = dinic(S, T);
        if(ans == n) puts("YES");
        else puts("NO");
    }
    return 0;
}
void buildGraph() {
    for(int i = 1; i <= n; i++) {
        addEdge(S, i, 1);
    }
    for(int i = 1; i <= m; i++) {
        addEdge(n + i, T, allPeople[i]);
    }
}
void addEdge(int u, int v, int w) {
    edges[edgesNum].to = v;
    edges[edgesNum].vol = w;
    edges[edgesNum].next = Head[u];
    Head[u] = edgesNum++;

    edges[edgesNum].to = u;
    edges[edgesNum].vol = 0;
    edges[edgesNum].next = Head[v];
    Head[v] = edgesNum++;
}
bool bfs(int s, int t) {
    met(dep, -1);
    dep[s] = 1;
    queue<int> q;
    q.push(s);
    while(!q.empty()) {
        int u = q.front();
        if(u == t) return 1;
        q.pop();
        for(int e = Head[u]; e != -1; e = edges[e].next) {
            int v = edges[e].to;
            if(dep[v] == -1 && edges[e].vol > 0 && v != s) {
                dep[v] = dep[u] + 1;
                q.push(v);
            }
        }
    }
    if(dep[t] == -1) return 0;
    return 0;
}
int dfs(int u, int flow) {
    if(u == T) return flow;
    for(int &e = cur[u]; ~e; e = edges[e].next) {
        int v = edges[e].to;
        if(dep[v] == dep[u] + 1 && edges[e].vol > 0) {
            int di = dfs(v, min(flow, edges[e].vol));
            if(di > 0) {
                edges[e].vol -= di;
                edges[e ^ 1].vol += di;
                return di;
            }
        }
    }
    return 0;
}
int dinic(int s, int t) {
    int max_flow = 0;
    while(bfs(s, t)) {
        for(int i = S; i <= T; i++) cur[i] = Head[i];
        while(int di = dfs(s, inf)) {
            max_flow += di;
        }
    }
    return max_flow;
}

AC思路二:
分析题目的数据量来看,有1e5个人,但是星球的个数为10个,那么分析来看最多有2^10=1024种不同的选择,又因为1e5 > 1024所以有很多人的选择是完全一模一样的,然后我们可以把这些人归为一类人,具体方法就是状态压缩,运用位运算把每个人压缩一下,然后选择完全一样的人就会被压缩到一起,这样做的话在建图的时候需要修改超级源点到人的容量以及人到星球的容量,压缩之后边的数目就会最复杂成为10240条,dinic就是可行的方案了。
AC代码:

#include <bits/stdc++.h>
using namespace std;
#define met(a, b) memset(a, b, sizeof(a))
const int inf = 0x7fffffff;
const int maxn = 1000000 + 5;
struct EDGE {
    int to, vol, next;
} edges[1000000];
int n, m, allPeople[maxn], edgesNum = 0, Head[maxn];
int dep[maxn], cur[maxn];
map<int, int> cnt;
int S, T, tot;
void buildGraph();
void addEdge(int u, int v, int w);
bool bfs(int s, int t);
int dfs(int u, int flow);
int dinic(int s, int t);
int main() {
    while(~scanf("%d%d", &n ,&m)) {
        S = 0;
        tot = 0;
        T = n + m + 1;
        cnt.clear();
        edgesNum = 0;
        met(Head, -1);
        int d, sum = 0;
        for(int i = 1; i <= n; i++) {
            int res = 0;
            for(int j = 1; j <= m; j++) {
                scanf("%d", &d);
                if(d == 1) res += (d << j);
            }
            cnt[res]++;
        }
        T = cnt.size() + m + 1;
        for(int i = 1; i <= m; i++) {
            scanf("%d", &allPeople[i]);
            sum += allPeople[i];
        }
        if(sum < n) {
            puts("NO");
            continue;
        }
        buildGraph();
        int ans = dinic(S, T);
        if(ans >= n) puts("YES");
        else puts("NO");
    }
    return 0;
}
void buildGraph() {
    map<int, int>::iterator it;
    for(it = cnt.begin(); it != cnt.end(); it++) {
        tot++;
        addEdge(S, tot, it -> second);
        for(int i = 1; i <= m; i++) {
            if((it -> first) & (1 << i)) {
                addEdge(tot, cnt.size() + i, it -> second);
            }
        }
    }

    for(int i = 1; i <= m; i++) {
        addEdge(tot + i, T, allPeople[i]);
    }
}
void addEdge(int u, int v, int w) {
    edges[edgesNum].to = v;
    edges[edgesNum].vol = w;
    edges[edgesNum].next = Head[u];
    Head[u] = edgesNum++;

    edges[edgesNum].to = u;
    edges[edgesNum].vol = 0;
    edges[edgesNum].next = Head[v];
    Head[v] = edgesNum++;
}
bool bfs(int s, int t) {
    met(dep, -1);
    dep[s] = 1;
    queue<int> q;
    q.push(s);
    while(!q.empty()) {
        int u = q.front();
        if(u == t) return 1;
        q.pop();
        for(int e = Head[u]; e != -1; e = edges[e].next) {
            int v = edges[e].to;
            if(dep[v] == -1 && edges[e].vol > 0 && v != s) {
                dep[v] = dep[u] + 1;
                q.push(v);
            }
        }
    }
    if(dep[t] == -1) return 0;
    return 0;
}
int dfs(int u, int flow) {
    if(u == T) return flow;
    for(int &e = cur[u]; ~e; e = edges[e].next) {
        int v = edges[e].to;
        if(dep[v] == dep[u] + 1 && edges[e].vol > 0) {
            int di = dfs(v, min(flow, edges[e].vol));
            if(di > 0) {
                edges[e].vol -= di;
                edges[e ^ 1].vol += di;
                return di;
            }
        }
    }
    return 0;
}
int dinic(int s, int t) {
    int max_flow = 0;
    while(bfs(s, t)) {
        for(int i = S; i <= T; i++) cur[i] = Head[i];
        while(int di = dfs(s, inf)) {
            max_flow += di;
        }
    }
    return max_flow;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值