POJ 1469 COURSES(二分图匹配)

本文提供了一道经典的二分图匹配问题的解决方案,通过两种不同的算法实现,分别是匈牙利算法和Hopcroft-Karp算法。探讨如何在给定的学生和课程条件下找到合适的课代表组合。

题目链接:http://poj.org/problem?id=1469

Description

Consider a group of N students and P courses. Each student visits zero, one or more than one courses. Your task is to determine whether it is possible to form a committee of exactly P students that satisfies simultaneously the conditions: 

  • every student in the committee represents a different course (a student can represent a course if he/she visits that course) 
  • each course has a representative in the committee 

Input

Your program should read sets of data from the std input. The first line of the input contains the number of the data sets. Each data set is presented in the following format: 

P N 
Count1 Student 1 1 Student 1 2 ... Student 1 Count1 
Count2 Student 2 1 Student 2 2 ... Student 2 Count2 
... 
CountP Student P 1 Student P 2 ... Student P CountP 

The first line in each data set contains two positive integers separated by one blank: P (1 <= P <= 100) - the number of courses and N (1 <= N <= 300) - the number of students. The next P lines describe in sequence of the courses �from course 1 to course P, each line describing a course. The description of course i is a line that starts with an integer Count i (0 <= Count i <= N) representing the number of students visiting course i. Next, after a blank, you抣l find the Count i students, visiting the course, each two consecutive separated by one blank. Students are numbered with the positive integers from 1 to N. 
There are no blank lines between consecutive sets of data. Input data are correct. 

Output

The result of the program is on the standard output. For each input data set the program prints on a single line "YES" if it is possible to form a committee and "NO" otherwise. There should not be any leading blanks at the start of the line.

Sample Input

2
3 3
3 1 2 3
2 1 2
1 1
3 3
2 1 3
2 1 3
1 1

Sample Output

YES
NO


题目大意:m个课程,n个学生,接下来m行,每行一个数cnt,接cnt个数,表示有cnt个编号为y的学生选了这门课,问能否找出一种关系,每一门课都有一个课代表(选了该门课的学生才能当且每个学生最多担任一门课程课代表)


题目思路:二分图最大匹配,看是否能得到m条匹配边,匈牙利算法和Hopcroft-Carp算法都可以,但差距就出来了,前者900多ms,后者500多ms(注意:不要cin,cin好像会超时)

代码:

/*#include<cstdio>
#include<cmath>
#include<cstring>
#include<string>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#include<queue>
#include<stack>
#include<map>

using namespace std;

#define FOU(i,x,y) for(int i=x;i<=y;i++)
#define FOD(i,x,y) for(int i=x;i>=y;i--)
#define MEM(a,val) memset(a,val,sizeof(a))
#define PI acos(-1.0)

const double EXP = 1e-9;
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x3f3f3f3f;
const ll MINF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9+7;
const int N = 505;

int line[N][N];  //i,j之间是否存在关系,即可以匹配,记得初始化
int used[N];     //每一轮寻找增广路径时第i个妹子是否被使用过
int Next[N];     //Next[i]=x代表第i个妹子和x号男生是匹配的,记得初始化
int n,m;

bool Find(int x)
{
    for(int i=1;i<=m;i++)
    {
        if(line[x][i]&&!used[i])
        {
            used[i] = 1;             //第i个妹子被使用了
            if(Next[i]==0||Find(Next[i]))   //判断第i个妹子是否有了男朋友,没有的话就可以匹配,有的话就把next[i]号男生转移掉再匹配
            {
                Next[i] = x;
                return true;
            }
        }
    }
    return false;
}

int maxMatch()
{
    memset(Next,0,sizeof(Next));
    int sum = 0;
    for(int i=1;i<=n;i++)
    {
        memset(used,0,sizeof(used));
        if(Find(i))
            sum++;
    }
    return sum;
}

int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    std::ios::sync_with_stdio(false);
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&m,&n);
        MEM(line,0);
        for(int i=1;i<=m;i++)
        {
            int x,y;
            scanf("%d",&x);
            while(x--)
            {
                scanf("%d",&y);
                line[y][i]=1;
            }
        }
        int ans = maxMatch();
        if(ans==m)
            puts("YES");
        else
            puts("NO");
    }
    return 0;
}
*/

#include<cstdio>
#include<cmath>
#include<cstring>
#include<string>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#include<queue>
#include<stack>
#include<map>

using namespace std;

#define FOU(i,x,y) for(int i=x;i<=y;i++)
#define FOD(i,x,y) for(int i=x;i>=y;i--)
#define MEM(a,val) memset(a,val,sizeof(a))
#define PI acos(-1.0)

const double EXP = 1e-9;
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x3f3f3f3f;
const ll MINF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9+7;
const int N = 505;

//时间复杂度O(sqrt(n)*E)

const int MAXN=350;// 最大点数
int bmap[MAXN][MAXN];//二分图
int cx[MAXN];//cx[i]表示左集合i顶点所匹配的右集合的顶点序号
int cy[MAXN]; //cy[i]表示右集合i顶点所匹配的左集合的顶点序号
int dis;
int n,m;
int dx[MAXN],dy[MAXN];  //dx表示到x的距离,dy表示到y的距离
int used[MAXN];        //在每次增广中是否使用i点

bool SearchPath()   //bfs寻找增广路集
{
    queue<int>Q;
    dis = INF;  //存每一次增广的距离
    memset(dx,-1,sizeof(dx));
    memset(dy,-1,sizeof(dy));
    for(int i=1;i<=n;i++)
    {
        if(cx[i]==-1)  ////将未遍历的节点入队,并初始化次节点距离为0
        {
            Q.push(i);
            dx[i] = 0;
        }
    }
    while(!Q.empty())
    {
        int u = Q.front();
        Q.pop();
        if(dx[u]>dis)
            break;
        //取右边的节点
        for(int v=1;v<=m;v++)
        {
            if(bmap[u][v]&&dy[v]==-1)
            {
                dy[v] = dx[u] + 1;  //v的距离为u的对应距离+1
                if(cy[v]==-1)       //如果该点未匹配,增广路形成
                    dis = dy[v];
                else                //如果该点已匹配,那么接着往下搜
                {
                    dx[cy[v]] = dy[v] + 1;
                    Q.push(cy[v]);
                }
            }
        }
    }
    return dis!=INF;
}

bool DFS(int u)
{
    for(int v=1;v<=m;v++)
    {
        //如果该点没有被使用过,并且距离为上一节点+1
        if(!used[v]&&bmap[u][v]&&dy[v]==dx[u]+1)
        {
            used[v] = 1;  //标记使用过该点
            //如果该点已经被匹配了并且为最后一个匹配点,那么这条路径不是增广路,即这条路的结点已经匹配
            if(cy[v]!=-1&&dy[v]==dis)
                continue;
            if(cy[v]==-1||DFS(cy[v]))
            {
                cy[v]=u,cx[u]=v;
                return true;
            }
        }
    }
    return false;
}

int MaxMatch()
{
    int sum = 0;
    memset(cx,-1,sizeof(cx));
    memset(cy,-1,sizeof(cy));
    while(SearchPath()) //当存在增广路,继续松弛
    {
        memset(used,0,sizeof(used));   //每一次的右边的点是否用过
        for(int i=1;i<=n;i++)
        {
            if(cx[i]==-1&&DFS(i))    //如果当前这个点没连过,且能找到增宽路
                sum++;
        }
    }
    return sum;
}


int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    std::ios::sync_with_stdio(false);
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&m,&n);
        MEM(bmap,0);
        for(int i=1;i<=m;i++)
        {
            int x,y;
            scanf("%d",&x);
            while(x--)
            {
                scanf("%d",&y);
                bmap[y][i]=1;
            }
        }
        int ans = MaxMatch();
        if(ans==m)
            puts("YES");
        else
            puts("NO");
    }
    return 0;
}

## 软件功能详细介绍 1. **文本片段管理**:可以添加、编辑、删除常用文本片段,方便快速调用 2. **分组管理**:支持创建多个分组,不同类型的文本片段可以分类存储 3. **热键绑定**:为每个文本片段绑定自定义热键,实现一键粘贴 4. **窗口置顶**:支持窗口置顶功能,方便在其他应用程序上直接使用 5. **自动隐藏**:可以设置自动隐藏,减少桌面占用空间 6. **数据持久化**:所有配置和文本片段会自动保存,下次启动时自动加载 ## 软件使用技巧说明 1. **快速添加文本**:在文本输入框中输入内容后,点击"添加内容"按钮即可快速添加 2. **批量管理**:可以同时编辑多个文本片段,提高管理效率 3. **热键冲突处理**:如果设置的热键与系统或其他软件冲突,会自动提示 4. **分组切换**:使用分组按钮可以快速切换不同类别的文本片段 5. **文本格式化**:支持在文本片段中使用换行符和制表符等格式 ## 软件操作方法指南 1. **启动软件**:双击"大飞哥软件自习室——快捷粘贴工具.exe"文件即可启动 2. **添加文本片段**: - 在主界面的文本输入框中输入要保存的内容 - 点击"添加内容"按钮 - 在弹出的对话框中设置热键和分组 - 点击"确定"保存 3. **使用热键粘贴**: - 确保软件处于运行状态 - 在需要粘贴的位置按下设置的热键 - 文本片段会自动粘贴到当前位置 4. **编辑文本片段**: - 选中要编辑的文本片段 - 点击"编辑"按钮 - 修改内容或热键设置 - 点击"确定"保存修改 5. **删除文本片段**: - 选中要删除的文本片段 - 点击"删除"按钮 - 在确认对话框中点击"确定"即可删除
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值