暑假笔记·匈牙利算法

匈牙利算法

由于博主太懒,详细讲解见详细讲解

例题:E. Maximize Mex

原题地址

There are n students and mm clubs in a college. The clubs are numbered from 1 to m. Each student has a potential pi and is a member of the club with index ci. Initially, each student is a member of exactly one club. A technical fest starts in the college, and it will run for the next dd days. There is a coding competition every day in the technical fest.

Every day, in the morning, exactly one student of the college leaves their club. Once a student leaves their club, they will never join any club again. Every day, in the afternoon, the director of the college will select one student from each club (in case some club has no members, nobody is selected from that club) to form a team for this day’s coding competition. The strength of a team is the mex of potentials of the students in the team. The director wants to know the maximum possible strength of the team for each of the coming dd days. Thus, every day the director chooses such team, that the team strength is maximized.

The mex of the multiset S is the smallest non-negative integer that is not present in S. For example, the mex of the{0,1,1,2,4,5,9} is 3, the mex of {1,2,3} is 0 and the mex of ∅ (empty set) is 0.

Input
The first line contains two integers n and mm (1≤m≤n≤5000), the number of students and the number of clubs in college.

The second line contains n integers p1,p2,…,pn (≤pi<5000), where pi is the potential of the i-th student.

The third line contains n integers c1,c2,…,cn (1≤ci≤m), which means that i-th student is initially a member of the club with index ci.

The fourth line contains an integer d (1≤d≤n), number of days for which the director wants to know the maximum possible strength of the team.

Each of the next d lines contains an integer ki (1≤ki≤n), which means that ki-th student lefts their club on the i-th day. It is guaranteed, that the ki-th student has not left their club earlier.

Output
For each of the d days, print the maximum possible strength of the team on that day.

Examples
input
5 3
0 1 2 2 0
1 2 2 3 2
5
3
2
4
5
1
output
3
1
1
1
0
input
5 3
0 1 2 2 1
1 3 2 3 2
5
4
2
3
5
1
output
3
2
2
1
0
input
5 5
0 1 2 4 5
1 2 3 4 5
4
2
3
5
4
output
1
1
1
1

Note
Consider the first example:

On the first day, student 3 leaves their club. Now, the remaining students are 1, 2, 4 and 5. We can select students 1, 2 and 4 to get maximum possible strength, which is 3. Note, that we can’t select students 1, 2 and 5, as students 2 and 5 belong to the same club. Also, we can’t select students 1, 3 and 4, since student 3 has left their club.

On the second day, student 2 leaves their club. Now, the remaining students are 1, 4 and 5. We can select students 1, 4 and 5 to get maximum possible strength, which is 1.

On the third day, the remaining students are 1 and 5. We can select students 1 and 5 to get maximum possible strength, which is 1.

On the fourth day, the remaining student is 1. We can select student 1 to get maximum possible strength, which is 1.

On the fifth day, no club has students and so the maximum possible strength is 0.

**人话翻译: **有n个俱乐部和m个学生,每个学生属于一个俱乐部。在之后的d天,每天要离开一个学生,而每个俱乐部每天要出一个人(此人不离开),凑成一个队伍,这个队伍的能力值必须为0到t的连续自然数,求t+1每天最大为多少。

代码:
首先,这道题用了一个小技巧,从前向后递推,看作每个俱乐部加人,而非减人。
由每个俱乐部出一个人,很容易联想到二分图,继而考虑匈牙利算法。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<cmath>
#include<iostream>
#include<map>
#include<queue>
#include<string>
#include<vector>
#include<utility>
#include<set>
#include<stack>
using namespace std;
const int maxn=5e3+10;;
int match[maxn],vis[maxn],ans[maxn],p[maxn],c[maxn],k[maxn],d,n,m;
int ma[maxn][maxn],book[maxn],f[maxn],to[maxn],cnt,nex[maxn];
void add(int a,int b)//加边
{
    cnt++;
    to[cnt]=b;
    nex[cnt]=f[a];
    f[a]=cnt;
}
bool dfs(int x)
{
    if(vis[x])return false;
    vis[x]=1;
    for(int i=f[x]; i; i=nex[i])
    {
        int v=to[i];
            if(match[v]==-1||dfs(match[v]))
            {
                match[v]=x;
                return true;//找到新增广路
            }
    }
    return false;//无法找到增广路
}
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=0; i<maxn; i++)match[i]=-1;
    for(int i=1; i<=n; i++)
        scanf("%d",&p[i]);
    for(int i=1; i<=n; i++)
        scanf("%d",&c[i]);
    scanf("%d",&d);
    for(int i=1; i<=d; i++)
    {
        scanf("%d",&k[i]);
        book[k[i]]=1;
    }
    for(int i=1; i<=n; i++)
        if(book[i]==0)add(p[i],c[i]);//初始化
    //以下开始每天加人
    for(int i=d; i>=1; i--)
    {
        ans[i]=ans[i+1];
        //尽管是从俱乐部中找自然数,但处理时可看作自然数找俱乐部
        //以自然数为左图
        while(1)
        {
            memset(vis,0,sizeof(vis));
            //每次加入新点时重新清零
            if(dfs(ans[i]))ans[i]++;
            else break;
        }
        int x=k[i];
        add(p[x],c[x]);//加入新边
    }
    for(int i=1; i<=d; i++)
        printf("%d\n",ans[i]);
    return 0;
}

例题:P1129 [ZJOI2007]矩阵游戏

原题地址

题目描述
小Q是一个非常聪明的孩子,除了国际象棋,他还很喜欢玩一个电脑益智游戏――矩阵游戏。矩阵游戏在一个N×N黑白方阵进行(如同国际象棋一般,只是颜色是随意的)。每次可以对该矩阵进行两种操作:

行交换操作:选择矩阵的任意两行,交换这两行(即交换对应格子的颜色)

列交换操作:选择矩阵的任意两列,交换这两列(即交换对应格子的颜色)

游戏的目标,即通过若干次操作,使得方阵的主对角线(左上角到右下角的连线)上的格子均为黑色。

对于某些关卡,小Q百思不得其解,以致他开始怀疑这些关卡是不是根本就是无解的!于是小Q决定写一个程序来判断这些关卡是否有解。

输入格式
第一行包含一个整数T,表示数据的组数。

接下来包含T组数据,每组数据第一行为一个整数N,表示方阵的大小;接下来N行为一个N×N的01矩阵(0表示白色,1表示黑色)。

输出格式
包含T行。对于每一组数据,如果该关卡有解,输出一行Yes;否则输出一行No。

输入输出样例
输入 #1
2
2
0 0
0 1
3
0 0 1
0 1 0
1 0 0
输出 #1
No
Yes

说明
N≤200

代码:
可以看出,本题可以转化为一个二分图,从每一列中选出一个格子,使图中每一行都有被选中的格子。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<cmath>
#include<iostream>
#include<map>
#include<queue>
#include<string>
#include<vector>
#include<utility>
#include<set>
#include<stack>
using namespace std;
int T,n;
int vis[620],match[620],tot=0,nxt[400020],to[400020],head[620];
void add(int a,int b)
{
	to[++tot]=b;
	nxt[tot]=head[a];
	head[a]=tot;
}
bool dfs(int x)//匈牙利算法模板
{
	int i,v;
	if(vis[x]) return false;
	vis[x]=1;
	for(i=head[x];i;i=nxt[i])
	{
		v=to[i];
		if(match[v]==-1||dfs(match[v]))
		{
			match[v]=x;
			return true;
		}
	}
	return false;
}
int main()
{
	ios::sync_with_stdio(false);
	int i,j,q,fl;
	cin>>T;
	while(T--)
	{
		fl=1;
		cin>>n;
		for(i=1;i<=n;i++) head[i]=0;
		for(i=1;i<=n;i++)
		{
			for(j=1;j<=n;j++)
			{
				cin>>q;
				if(q) add(j,i);//列为左图,行为右图
			}
		}
		for(i=1;i<=n;i++) match[i]=-1;//match[i]对应右图
		for(i=1;i<=n;i++)
		{
			memset(vis,0,sizeof(vis));//每次加入新点时重新归零
			if(!dfs(i))
			{
				fl=0;
				break;
			}
		}
		if(!fl) printf("No\n");
		else printf("Yes\n");
	}
    return 0;
}

KM算法

KM算法是在匈牙利算法的基础上演变出的,时间复杂度为O(n^3)。由于博主太懒,详细讲解见详细讲解

例题:奔小康赚大钱

原题地址

Problem Description

传说在遥远的地方有一个非常富裕的村落,有一天,村长决定进行制度改革:重新分配房子。
这可是一件大事,关系到人民的住房问题啊。村里共有n间房间,刚好有n家老百姓,考虑到每家都要有房住(如果有老百姓没房子住的话,容易引起不安定因素),每家必须分配到一间房子且只能得到一间房子。
另一方面,村长和另外的村领导希望得到最大的效益,这样村里的机构才会有钱.由于老百姓都比较富裕,他们都能对每一间房子在他们的经济范围内出一定的价格,比如有3间房子,一家老百姓可以对第一间出10万,对第2间出2万,对第3间出20万.(当然是在他们的经济范围内).现在这个问题就是村领导怎样分配房子才能使收入最大.(村民即使有钱购买一间房子但不一定能买到,要看村领导分配的).

Input

输入数据包含多组测试用例,每组数据的第一行输入n,表示房子的数量(也是老百姓家的数量),接下来有n行,每行n个数表示第i个村名对第j间房出的价格(n<=300)。

Output

请对每组数据输出最大的收入值,每组的输出占一行。

Sample Input
2
100 10
15 23

Sample Output

123

代码:
KM算法入门题,本文代码受参考博客启发。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<cmath>
#include<iostream>
#include<map>
#include<queue>
#include<string>
#include<vector>
#include<utility>
#include<set>
#include<stack>
using namespace std;
#define inf 1e9
int lx[305],ly[305],slack[305],match[305],visx[305],visy[305];
int w[305][305],n,ans;
//lx[i],ly[i]存储顶值,slack[i]存储修改量
bool dfs(int x)//类似匈牙利算法
{
    int i;
    if(visx[x]) return false;//
    visx[x]=1;
    for(i=1;i<=n;i++)
    {
        if(visy[i]) continue;
        if(w[x][i]==lx[x]+ly[i])
        {
            visy[i]=1;
            if(!match[i]||dfs(match[i]))
            {
                match[i]=x;
                return true;
            }
        }
        else slack[i]=min(slack[i],lx[x]+ly[i]-w[x][i]);//匈牙利算法中没有的
    }
    return false;//没找到增广路
}
void km()
{
    int i,j,tmp,k;
    memset(match,0,sizeof(match));
    memset(ly,0,sizeof(ly));
    memset(lx,0,sizeof(lx));
    for(i=1;i<=n;i++)
        for(j=1;j<=n;j++)
        {
            lx[i]=max(lx[i],w[i][j]);
        }
    for(k=1;k<=n;k++)
    {
        for(i=1;i<=n;i++) slack[i]=inf;
        while(2)
        {
            memset(visx,0,sizeof(visx));
            memset(visy,0,sizeof(visy));
            if(dfs(k)) break;//配对成功
            tmp=inf;
            for(i=1;i<=n;i++)
            {
                if(!visy[i])
                {
                    if(tmp>slack[i]) tmp=slack[i];
                }
            }
            //if(tmp==inf) return;
            for(i=1;i<=n;i++)
            {
                if(visx[i]) lx[i]-=tmp;
                if(visy[i]) ly[i]+=tmp;
                //修改顶值
                else slack[i]-=tmp;
                //lx减少了,所以slack也减少了
            }
        }
    }
}
int main ()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    //值得注意的是,这题卡了输入输出,cin输入会TLE
    int i,j;
    while(scanf("%d",&n)!=EOF)
    {
        ans=0;
        for(i=1;i<=n;i++)
            for(j=1;j<=n;j++)
                scanf("%d",&w[i][j]);
        km();
        for(i=1;i<=n;i++)
            ans=ans+w[match[i]][i];
        printf("%d\n",ans);
    }
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值