HDU 3395Special Fish(二分图的最大权匹配)

B - Special Fish
Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u

Description

There is a kind of special fish in the East Lake where is closed to campus of Wuhan University. It’s hard to say which gender of those fish are, because every fish believes itself as a male, and it may attack one of some other fish who is believed to be female by it.
A fish will spawn after it has been attacked. Each fish can attack one other fish and can only be attacked once. No matter a fish is attacked or not, it can still try to attack another fish which is believed to be female by it.
There is a value we assigned to each fish and the spawns that two fish spawned also have a value which can be calculated by XOR operator through the value of its parents.
We want to know the maximum possibility of the sum of the spawns.
 

Input

The input consists of multiply test cases. The first line of each test case contains an integer n (0 < n <= 100), which is the number of the fish. The next line consists of n integers, indicating the value (0 < value <= 100) of each fish. The next n lines, each line contains n integers, represent a 01 matrix. The i-th fish believes the j-th fish is female if and only if the value in row i and column j if 1.
The last test case is followed by a zero, which means the end of the input.
 

Output

Output the value for each test in a single line.
 

Sample Input

     
     
3 1 2 3 011 101 110 0
 

Sample Output

     
     
6
 
先预处理一下,对于w[i][j]表示x中的i点和y集合中的j点间的边的权值,即为i,j的异或值;然后就是纯的二分图最大权值匹配了,使用KM算法,白书上讲,使用KM算法来求权值和最大的完美匹配。给每个节点搞个顶标使得对于任意(x,y)l(x)+l(y)>=w(x,y);
定理一:设 L 是二部图 G 的可行顶标。若 L 等价子图 GL 有完美匹配 M,则 M 是 G 的最佳匹配;
白书上是这样描述的算法:任意构造一个可行的顶标,然后求相等子图的最大匹配,如果存在完美匹配,算法终止,否则修改顶标,使得相等子图边变多,才有更大的机会存在完美匹配。                                             
感觉还是不理解算法的实质,具体参见一下博客:http://blog.sina.com.cn/s/blog_691ce2b701016reh.html
                                             http://www.cnblogs.com/skyming/archive/2012/02/18/2356919.html
 
   
 
   
代码:(一般的算法是O(n^4)),这里白书上提供了一种优化,用slack数组来存松弛量,这样把复杂度降到了O(n^3),不过前者的确更易懂;
#include<iostream>
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
#define max(a,b) a>b?a:b
#define min(a,b) a<b?a:b
#define MAX 110
#define inf 1<<30
using namespace std;
bool s[MAX],t[MAX];///s,t,代表左右两个集合的点是否被访问
int w[MAX][MAX];//权值
int lx[MAX],ly[MAX];//lx,ly代表左右两个集合中的点的顶标
int lef[MAX];///右边点的左边匹配点
int slack[MAX];//松弛度
int N[MAX],n;//N代表本来的价值(对于本体而言的)
bool dfs(int x)//深搜找增广路
{
    int i;
    s[x]=true;
    for(i=1;i<=n;i++)
    {
        if(!t[i])
        {
            int d=lx[x]+ly[i]-w[x][i];///松弛量
            if(d==0)
            {
                t[i]=true;
                if((!lef[i])||dfs(lef[i]))
                {
                    lef[i]=x;
                    return true;
                }
            }
            else
            {
                slack[i]=min(slack[i],d);
            }
        }
    }
    return false;
}
void update()///更新顶标值
{
    int i,d=inf;
    for(i=1;i<=n;i++) if(!t[i])
        d=min(d,slack[i]);
    for(i=1;i<=n;i++)
    {
        if(s[i])
        {
            lx[i]-=d;
        }
    }
    for(i=1;i<=n;i++)
    {
        if(t[i])
        {
            ly[i]+=d;
        }
        else
            slack[i]-=d;
    }
}
void init()//初始化
{
    int i,j;
    memset(lef,0,sizeof(lef));
    memset(ly,0,sizeof(ly));
    for(i=1;i<=n;i++)
    {
        for(j=1,lx[i]=0;j<=n;j++)
        {
            lx[i]=max(lx[i],w[i][j]);//顶标初始化为权值的最大的
        }
    }

}
int KM()
{
    int i,j;
    init();
    for(i=1;i<=n;i++)
    {
        for(j=1;j<=n;j++)
        {
            slack[j]=inf;;
        }///初始化松弛度
        while(1)
        {
            for(j=1;j<=n;j++)
            {
                s[j]=t[j]=0;
            }
            if(dfs(i))///找到退出
                break;
            else
                update();///更新顶标值
        }
    }
    int result=0;
    for(i=1;i<=n;i++)
    {
        if(lef[i]>0)
        {
            result+=w[lef[i]][i];///计算和()
        }
    }
    return result;
}
int main()
{
    int i,j,x,ans;
    while(~scanf("%d",&n)&&n)
    {
        for(i=1;i<=n;i++)
        {
            scanf("%d",&N[i]);
        }
        for(i=1;i<=n;i++)
        {
            for(j=1;j<=n;j++)
            {
                scanf("%1d",&x);
                x==1?w[i][j]=N[i]^N[j]:w[i][j]=0;
            }
        }
        ans=KM();
        printf("%d\n",ans);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值