GYM 101755 D.Transfer Window(Floyd+二分匹配-hungary+dfs)

125 篇文章 0 订阅
61 篇文章 0 订阅

Description

共有 n n 名队员,队伍里已有k名队员 a1,...,ak a 1 , . . . , a k ,但是期望队伍里有的队员是 b1,...,bk b 1 , . . . , b k ,已知矩阵 Ai,j A i , j ,其中 Ai,j=1 A i , j = 1 表示队伍中的 i i 队员可以换成j队员( i i 队员在队伍里且j队员不在队伍里的前提下),问是否存在一个换人序列可以把这 k k 名队员换成理想的k名队员

Input

第一行两个整数 n,k n , k 表示队员总数和队伍中队员数量,之后 k k 个整数a1,...,ak表示已在队伍的队员,之后 k k 个整数b1,...,bk表示期望在队伍的队员,最后一个 n×n n × n 01 01 矩阵 A A (1kn300)

Output

如果存在一个合法的换人序列则输出 YES Y E S 和换人操作次数以及具体操作,否则输出 NO N O

Sample Input

5 2
1 2
4 5
00100
00100
00011
00000
00000

Sample Output

YES
4
1 3
3 4
2 3
3 5

Solution

首先用 Floyd F l o y d 把一步可达矩阵变成可达矩阵,对于已有队员集合和期望队员集合,去掉其中相同的队员,假设剩下 m m 个人,根据可达矩阵对这m对人建二分图求最大匹配,如果最大匹配小于 m m 说明无解,否则说明有一种方案可以把已有的m个人分别与期望的 m m 个人一一对应,但是注意到这m个一一对应事实上是 m m 条换人的路径,而这m条路径可能会交叉,例如在把已有一名队员变成期望的一名队员时,交换路径中可能出现了一个也在已有队员集合中的队员,这是不合法的,所以在考虑具体方案时需要做一些处理,在依次处理这 m m 条路径时,对于当前处理的路径,假设从1 a a (用数字表示在队伍中的队员,用小写字母表示不在队伍中的队员)的路径是:

1bc2dea

如果直接按顺序把这个换人序列输出的话,由于 2 2 已经在队伍中,不可能用c 2 2 再换到队伍中,所以要对这个序列做一些处理,注意到只要我们把该操作序列变成2dea 1bc2 1 → b → c → 2 就可以避免冲突,即从目标点往前,遇到一个在队伍中的点就把已有操作序列输出,之后继续往前直至到起点,如此操作可以使得从 1 1 换到a的过程中,只改变了 1 1 a的归属而不会影响其他点

Code

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<ctime>
using namespace std;
typedef long long ll;
typedef pair<int,int>P;
const int INF=0x3f3f3f3f,maxn=305;
int uN,vN;//u,v数目
int g[maxn][maxn];//编号是0~n-1的 
int linker[maxn];
bool used[maxn];
bool dfs(int u)
{
    int v;
    for(v=0;v<vN;v++)
        if(g[u][v]&&!used[v])
        {
            used[v]=true;
            if(linker[v]==-1||dfs(linker[v]))
            {
                linker[v]=u;
                return true;
            }    
        }  
    return false;  
}    
int hungary()
{
    int res=0;
    int u;
    memset(linker,-1,sizeof(linker));
    for(u=0;u<uN;u++)
    {
        memset(used,0,sizeof(used));
        if(dfs(u))  res++;
    } 
    return res;   
}  
int n,k,m[maxn][maxn],G[maxn][maxn],va[maxn],vb[maxn],vis[maxn];
char s[maxn];
vector<P>ans,S;
int dfs(int u,int v)
{
    vis[u]=1;
    if(u==v)return 1;
    for(int w=1;w<=n;w++)
        if(G[u][w]&&!vis[w])
        {
            if(dfs(w,v))
            {
                S.push_back(P(u,w));
                if(va[u])
                {
                    for(int i=S.size()-1;i>=0;i--)ans.push_back(S[i]);
                    S.clear();
                }
                return 1;
            }
        }
    return 0;
}
int main()
{
    while(~scanf("%d%d",&n,&k))
    {
        memset(va,0,sizeof(va));
        memset(vb,0,sizeof(vb));
        int temp;
        for(int i=0;i<k;i++)scanf("%d",&temp),va[temp]=1;
        for(int i=0;i<k;i++)scanf("%d",&temp),vb[temp]=1;
        for(int i=1;i<=n;i++)
        {
            scanf("%s",s+1);
            for(int j=1;j<=n;j++)
                G[i][j]=m[i][j]=s[j]-'0';
        }
        for(int l=1;l<=n;l++)
            for(int i=1;i<=n;i++)
                for(int j=1;j<=n;j++)
                    if(m[i][l]&&m[l][j])m[i][j]=1;
        memset(g,0,sizeof(g));
        int res=0;
        for(int i=1;i<=n;i++)
            if(va[i]&&!vb[i])
            {
                res++;
                for(int j=1;j<=n;j++)
                    if(vb[j]&&!va[j]&&m[i][j])g[i-1][j-1]=1;
            }
        vN=uN=n;
        if(hungary()!=res)printf("NO\n");
        else
        {
            printf("YES\n");
            ans.clear();
            S.clear();
            for(int i=1;i<=n;i++)
                if(vb[i])
                {
                    if(va[i])continue;
                    int u=linker[i-1]+1,v=i;
                    memset(vis,0,sizeof(vis));
                    dfs(u,v);
                    va[u]=0,va[v]=1;
                }
            printf("%d\n",ans.size());
            for(int i=0;i<ans.size();i++)printf("%d %d\n",ans[i].first,ans[i].second,i==ans.size()-1?'\n':' ');
        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值