HDU3605 Escape(网络流,最大流,状态压缩,ISAP算法)

Problem Description

2012 If this is the end of the world how to do? I do not know how. But
now scientists have found that some stars, who can live, but some
people do not fit to live some of the planet. Now scientists want your
help, is to determine what all of people can live in these planets.

Input

More set of test data, the beginning of each data is n (1 <= n <=
100000), m (1 <= m <= 10) n indicate there n people on the earth, m
representatives m planet, planet and people labels are from 0. Here
are n lines, each line represents a suitable living conditions of
people, each row has m digits, the ith digits is 1, said that a person
is fit to live in the ith-planet, or is 0 for this person is not
suitable for living in the ith planet. The last line has m digits, the
ith digit ai indicates the ith planet can contain ai people most.. 0
<= ai <= 100000

Output

Determine whether all people can live up to these stars If you can
output YES, otherwise output NO.

Sample Input

1 1
1
1

2 2
1 0
1 0
1 1

Sample Output

YES
NO

思路

先说题意,这题题意很简单,主要是卡时间,需要用到二进制来进行状态压缩,很巧妙.

第一行给了一个n,m表示有n个人,要住在m个星球上。
然后是一个n*m的矩阵,记做a[i][j],表示第i个人,对j星球的态度。(0表示不想去,1表示想去)。

问人能不能全部住在自己喜欢的星球上,输出YESNO

首先思路很明确,就是找一个源点和汇点,把源点和每一个人相连,边权为1,然后把人和他想要去的星球相连,边权为1,把星球和汇点相连,边权为星球的容量,但是由于点的数量太多了,这种方法不可行,肯定会超时的。。

那我们就应该换一种思路,考虑一下二进制,星球最多有10个,,每一个人都会对10个星球表示出喜欢或者不喜欢,那么那么一共就可能有 210=1024 种情况,我们可以转化为最多1024种状态,记录一下每个状态的出现次数,那么连边方式就这样:

  • 把源点和每个状态相连,边权为该状态出现的次数
  • 把每个状态和星球相连,连接的方式为二进制中的1的位置,边权为该状态出现的次数
  • 把星球和汇点相连,边权为该星球的容量
  • 起点是:0
  • 状态的范围:1 ~ (1<<m)
  • 星球的范围:(1<<m) ~ (1<<m)+m
  • 终点:(1<<m)+m+1

有了建边方式,代码就很好写了。

代码

#include <cstdio>
#include <cstring>
#include <cctype>
#include <stdlib.h>
#include <string>
#include <map>
#include <iostream>
#include <stack>
#include <cmath>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
#define inf 0x3f3f3f3f
#define mem(a,b) memset(a,b,sizeof(a))
const int N=2000+20;
const int M=N*N;
int top;
int h[N],pre[N],g[N];//h[i]记录每个节点的高度,pre[i]记录前驱,g[i]表示距离为i个节点数有多少个
int first[N];
bool vis[N];
struct node
{
    int v,next;
    int cap,flow;
} E[M*2];
void init()
{
    mem(first,-1);
    top=0;
}
void add_edge(int u,int v,int c)
{
    E[top].v=v;
    E[top].cap=c;
    E[top].flow=0;
    E[top].next=first[u];
    first[u]=top++;
}
void add(int u,int v,int c)
{
    add_edge(u,v,c);
    add_edge(v,u,0);
}

void set_h(int t)//标高函数,从终点向起点标高
{
    queue<int>q;
    mem(h,-1);
    mem(g,0);
    h[t]=0;
    q.push(t);
    while(!q.empty())
    {
        int v=q.front();
        q.pop();
        g[h[v]]++;//当前高度的个数+1
        for(int i=first[v]; ~i; i=E[i].next)
        {
            int u=E[i].v;
            if(h[u]==-1)//当前节点未标高
            {
                h[u]=h[v]+1;
                q.push(u);
            }
        }
    }
}

int Isap(int s,int t,int n)//isap算法
{
    set_h(t);
    int ans=0,u=s;
    int d;
    while(h[s]<n)//节点的高度小于顶点数
    {
        int i=first[u];
        if(u==s) d=inf;
        for(; ~i; i=E[i].next)
        {
            int v=E[i].v;
            if(E[i].cap>E[i].flow&&h[u]==h[v]+1)//容量大于流量且当前的高度等于要去的高度+1
            {
                u=v;
                pre[v]=i;
                d=min(d,E[i].cap-E[i].flow);//找最小增量
                if(u==t)//到达汇点
                {
                    while(u!=s)
                    {
                        int j=pre[u];//找到u的前驱
                        E[j].flow+=d;//正向流量+d
                        E[j^1].flow-=d;//反向流量-d
                        u=E[j^1].v;//向前搜索
                    }
                    ans+=d;
                    d=inf;
                }
                break;
            }
        }
        if(i==-1)//邻接边搜索完毕,无法行进
        {
            if(--g[h[u]]==0)//重要的优化,这个高度的节点只有一个,结束
                break;
            int hmin=n-1;//重贴标签的高度初始为最大
            for(int j=first[u]; ~j; j=E[j].next)
            {
                if(E[j].cap>E[j].flow)
                    hmin=min(hmin,h[E[j].v]);//取所有邻接点高度的最小值
            }
            h[u]=hmin+1;//重新标高
            g[h[u]]++;//标高后该高度的点数+1
            if(u!=s)//不是源点时,向前退一步,重新搜
                u=E[pre[u]^1].v;
        }
    }
    return ans;
}
inline int read()
{
    char ch=getchar();
    int x=0,f=1;
    while(ch>'9'||ch<'0')
    {
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch<='9'&&ch>='0')
    {
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
int x,state,a[N],n,m;
void build()
{
    int st=0;
    int ed=(1<<m)+m+1;
    memset(a,0,sizeof(a));
    for(int i=1; i<=n; i++)
    {
        state=0;
        for(int j=0; j<m; j++)
        {
            x=read();
            if(x) state|=(1<<j);
        }
        a[state]++;//记录当前状态的个数
    }
    for(int i=0; i<(1<<m); i++)
    {
        if(a[i])
        {
            add(st,i+1,a[i]);//从源点到对应状态的个数
            for(int j=0; j<m; j++)
            {
                if(i&(1<<j))//取出i的第j位
                    add(i+1,j+1+(1<<m),a[i]);//建立从状态到星球的边
            }
        }
    }
    for(int i=0; i<m; i++)//每个房子的容量
    {
        x=read();
        add((1<<m)+i+1,ed,x);
    }
    if(Isap(st,ed,ed+2)==n)puts("YES");
    else puts("NO");
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        init();
        build();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值