M - Escape - HDU 3605 - (缩点+最大流SAP)

题目大意:2012世界末日来了,科学家发现了一些星球可以转移人口,不过有的人可以在一些星球上生存有的人不行,而且每个星球都有一定的承载量,现在想知道是否所有的人都可以安全转移呢?

输入:首先输入一个N和M,表示人数和星球数,接着输入N行,每行有M个01组成的数,0表示第Ni个人无法再Mj号星球上生存,1表示可以生存,最后一行是每个星球的最大承载量。

分析:刚看的时候是一道比较裸的最大流题目,只要求出来最大流是否等于总人口数就行了,不过人的数量貌似是有点多的,刚开始没有管那么多直接上了最大流,不过也果然TLE,后来借鉴了一下别人的想法,就是缩点,我们发现M的值是特别小的,最大只有10,这就意味着有很多人的状态是相同的(2^10),所以可以把这些状态相同的人压缩到一起,这样最多也就1024个人了,大大缩减了复杂度,不过很不幸依然TLE!!!,好吧,认了,只能再去找一下sap的模板带入一下,刚开始随意找了一个模板套上,不过WA了,也不知道出了什么问题,,因为不了解SAP这东西,没办法,只能学一下SAP了,在网上找了一个很不错的演示 。其实和dinic还是挺相似的,只不过这个只做了一次的DFS是从汇点到源点进行的分层,然后寻找可行弧(也就是下面有没有可以与之相连的边),如果没有可行弧,就修改他的层号,然后就这样一直找下去,直到源点的层号大于总点数或者出现断层就可以停止了。ps.生命不息,学习不止啊。

sap演示 下载

下面是AC代码。

======================================================================================

#include<stdio.h>
#include<string.h>
#include<queue>
#include<math.h>
#include<algorithm>
using namespace std;

const int MAXN = 1110;
const int oo = 1e9+7;

struct Edge{int u, v, flow, next;}edge[MAXN*100];
int Head[MAXN], cnt;
int used[MAXN], cur[MAXN], Stack[MAXN];
int Layer[MAXN], gap[MAXN], cntv;///节点的总个数

void InIt()
{
    cnt = 0;
    memset(Head, -1, sizeof(Head));
    memset(used, 0, sizeof(used));
}
void AddEdge(int u, int v, int flow)
{
    edge[cnt].u = u;
    edge[cnt].v = v;
    edge[cnt].flow = flow;
    edge[cnt].next = Head[u];
    Head[u] = cnt++;

edge[cnt].u = v;
    edge[cnt].v = u;
    edge[cnt].flow = 0;
    edge[cnt].next = Head[v];
    Head[v] = cnt++;
}

void BFS(int End)
{
    memset(Layer, -1, sizeof(Layer));
    memset(gap, 0, sizeof(gap));
    queue<int> Q;
    Q.push(End);
    Layer[End] = 0, gap[0] = 1;

while(Q.size())
    {
        int u = Q.front();
        Q.pop();

for(int j=Head[u]; j!=-1; j=edge[j].next)
        {
            int v = edge[j].v;

if(Layer[v] == -1)
            {
                Layer[v] = Layer[u] + 1;
                gap[Layer[v]]++;
                Q.push(v);
            }
        }
    }
}

int SAP(int start, int End)
{
    int j, top=0, u = start, MaxFlow=0;

BFS(End);
    cntv = End;
    memcpy(cur, Head, sizeof(Head));

while(Layer[start] < cntv)
    {///源点的层次小于总结点数,汇点是0层

if(u == End)
        {
            int MinFlow = oo, location;///记录下最小流量边的位置,出栈时候用

for(j=0; j<top; j++)
            {
                int i = Stack[j];
                if(MinFlow > edge[i].flow)
                {
                    MinFlow = edge[i].flow;
                    location = j;
                }
            }
            for(j=0; j<top; j++)
            {///所有的边减去路径上的最小流量
                int i = Stack[j];

edge[i].flow -= MinFlow;
                edge[i^1].flow += MinFlow;
            }

MaxFlow += MinFlow;
            top = location;///退栈
            u = edge[Stack[top]].u;
        }
        else if(gap[Layer[u]-1] == 0)
            break;///u所在的层下面的层没有了,出现了断层,也就没有了可行弧

for(j=cur[u]; j!=-1; j=edge[j].next)
        {///如果u有可行弧就停止
            if(Layer[u]==Layer[edge[j].v]+1 && edge[j].flow)
                break;
        }

if(j != -1)
        {///找到了可行弧
            cur[u] = j;///u点的可行弧是j
            Stack[top++] = j;///记录下这条边
            u = edge[j].v;
        }
        else
        {///没有找到可行弧,修改标号
            int MinIndex = cntv;

for(j=Head[u]; j!=-1; j=edge[j].next)
            {///查找与u相连的最小的层是多少
                if(edge[j].flow && MinIndex > Layer[edge[j].v])
                {///记录下这条可行弧,下次可以直接访问这条边
                    MinIndex = Layer[edge[j].v];
                    cur[u] = j;
                }
            }

gap[Layer[u]] -= 1;///u改变层,所以u原来所在层的点数减去1
            Layer[u] = MinIndex + 1;
            gap[Layer[u]] += 1;

if(u != start)
            {///返回上一层
                u = edge[Stack[--top]].u;
            }
        }
    }

return MaxFlow;
}

int main()
{
    int N, M;

while(scanf("%d%d", &N, &M) != EOF)
    {
        int i, j, u, Ni=pow(2, M), start=Ni+M+1, End=start+1;
        char ch;

InIt();

for(i=1; i<=N; i++)
        {
            u = 0;
            for(j=1; j<=M; j++)
            {
                while(ch = getchar(), ch ==‘ ‘ || ch == ‘\n‘);
                u = u*2 + ch-‘0‘;
            }
            used[u]++;///这种状态的人+1
        }

for(i=1; i<Ni; i++)
        {
            if(used[i])
            {
                AddEdge(start, i, used[i]);
            }
        }

for(i=1; i<=M; i++)
        {
            scanf("%d", &u);
            AddEdge(i+Ni, End, u);
        }

for(i=1; i<Ni; i++) if(used[i])
        {///如果这种状态有人
            u = i;
            for(j=M; j>0; j--)
            {
                if(u&1)
                {///最后一位是1
                    AddEdge(i, Ni+j, used[i]);
                }
                u = u >> 1;
            }
        }

int MaxFlow = SAP(start, End);

if(MaxFlow == N)
            printf("YES\n");
        else
            printf("NO\n");
    }

return 0;
}

时间: 2024-10-15 18:03:22

M - Escape - HDU 3605 - (缩点+最大流SAP)的相关文章

hdu 3605 /状态合并最大流

题意:N个人去m个星球,给出n个人可以去哪些星球的01矩阵.求是否能满足所有人都去.(n到10万,m<=10) 一看,起先一瞬间就建图,准备秒了,人向星球连边,直接最大流判断是否为n,提交超时...是啊,10W*10=100W条边,铁定超时.. 后来经牛提示:注意,m<10!  人的可以去星球,一共最多有10个,那只有 2^10次种情况,就是说x部与Y部连线情况很多点是一样的(所给的01矩阵,最多10W行,10列,必然有很多行是一样的).所以X部只留1024个点,这些点中,点i含j个人的状态,

HDU 3468 Treasure Hunting (最大流SAP)经典(看似广搜实则最大流)

Treasure Hunting Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 131072/65536 K (Java/Others) Total Submission(s): 1532    Accepted Submission(s): 400 Problem Description Do you like treasure hunting? Today, with one of his friend, iSea is on

HDU 3605 Escape(最大流+缩点转换)

http://acm.hdu.edu.cn/showproblem.php?pid=3605 题目很简单,要求的就是最后能搬到星球上去的人的个数.刚开始看到,知道是最大流,就把人和星球都设为点,能生存就连线,权值为1,最后建立超级源点和超级汇点.求出最大流量即可.先是RE,开大数组后TLE.仔细算了,光光人到星球的便就可达到100w了,超时的概率太大了.后来找了解题报告,知道了缩点这一说,因为星球个数m最大只有10个,所以每个人最多只有1024种情况,把这每一种情况设为点(这里很抽象),将之与符

Hdu 3605 Escape (最大流 + 缩点)

题目链接: Hdu 3605  Escape 题目描述: 有n个人要迁移到m个星球,每个星球有最大容量,每个人有喜欢的星球,问是否所有的人都能迁移成功? 解题思路: 正常情况下建图,不会爆内存,但是TLE还是稳稳的.以前只遇到过网络流拆点建图,这个正好是缩点建图.吼吼吼~~~,建图的方式还是值得学习的. 因为星球数目最多十个,那么无论有多少个人,其不同选择也就2^10种咯.把不同的选择作为节点,节点就从10^5减少到了2^10,整整缩小了一个数量级呢.建立源点和汇点,源点和选择链接,边权为这种选

HDU 3605 Escape(状压+最大流)

Escape Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Total Submission(s): 9430    Accepted Submission(s): 2234 Problem Description 2012 If this is the end of the world how to do? I do not know how. But now scient

HDU 3605 Escape【二分图多重匹配】

题意: 有n个人去m个星球  告诉你每个人想去哪些星球和每个星球最多容纳多少人,问能不能让所有人都满足 分析: 二分图多重匹配 代码: 1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <vector> 5 using namespace std; 6 7 const int maxn = 100005; 8 const int maxm = 15; 9 10

hdu 3605 Escape (二分图多重匹配)

Escape Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 4298    Accepted Submission(s): 1129 Problem Description 2012 If this is the end of the world how to do? I do not know how. But now scient

HDU 2732 Leapin&#39; Lizards(最大流)

Leapin' Lizards Problem Description Your platoon of wandering lizards has entered a strange room in the labyrinth you are exploring. As you are looking around for hidden treasures, one of the rookies steps on an innocent-looking stone and the room's

POJ 2135 Farm Tour &amp;&amp; HDU 2686 Matrix &amp;&amp; HDU 3376 Matrix Again 费用流求来回最短路

累了就要写题解,最近总是被虐到没脾气. 来回最短路问题貌似也可以用DP来搞,不过拿费用流还是很方便的. 可以转化成求满流为2 的最小花费.一般做法为拆点,对于 i 拆为2*i 和 2*i+1,然后连一条流量为1(花费根据题意来定) 的边来控制每个点只能通过一次. 额外添加source和sink来控制满流为2. 代码都雷同,以HDU3376为例. #include <algorithm> #include <iostream> #include <cstring> #in