[BZOJ 1059] 矩阵游戏 Hungary算法

题目传送门:【BZOJ 1059】

题目大意:小 Q 很喜欢玩矩阵游戏。矩阵游戏在一个 N * N 黑白方阵进行。每次可以对该矩阵进行两种操作:行交换操作:选择矩阵的任意两行,交换这两行(即交换对应格子的颜色)列交换操作:选择矩阵的任意行列,交换这两列(即交换对应格子的颜色)游戏的目标,即通过若干次操作,使得方阵的主对角线(左上角到右下角的连线)上的格子均为黑色。对于某些关卡,小 Q 百思不得其解,以致他开始怀疑这些关卡是不是根本就是无解的!!于是小 Q 决定写一个程序来判断这些关卡是否有解。

输入共 T 组数据。每组数据第一行为一个整数 N,表示一个 N * N 的矩阵;之后输入这个 01 矩阵(0 表示白色,1 表示黑色)。当这组数据有解时,输出 Yes;否则输出 No。


题目分析: (高能!)

(第一次做这道题中……)(结果:WA
这道题不就是一道贪心水题吗?直接判断每行每列是否全为白色,如果是就直接输出 No,否则输出 Yes 不就行了吗?啊哈哈哈哈哈…… 啊!(被一道从天而降的 WA 闪电击中,倒地不起)

(第二次做这道题中……)(结果:AC
经历了上次的教训之后,我仔细地看了看这道题,发现:这道题不能这样贪心!每行每列至少有一个黑色只是一项基本条件(必要条件)而已,不能直接判断!例如这组数据:

4
1 0 0 0
1 0 0 0
0 1 1 0
0 1 0 1

我们会发现,无论怎么样调整行列,都无法得到满足条件的答案。
事实上,用贪心来做这道题应该是行不通的,至少我构思了好几种办法,都失败了。

(然后继续 YY 算法中……)
~~(结合着之前做题的经验,我又接着试了试二分图匹配)~~
能不能这样呢?对于行和列构造出一个二分图,然后,如果当第 i 行第 j 列的数为 1,那么就在二分图对应的地方连一条双向边?那此时又怎么统计并判断答案呢?我惊奇地发现,只要是符合题目条件的答案,它的最大匹配数目都等于 N(换言之,它一定是一个完全匹配)。

那么又该怎么证明呢?这个问题,我还暂时没思考出答案。有兴趣的 OIer 可以自己先看看其他的博客吧 :-) 我一定会在之后补上 233333

下面附上代码:

  1. #include<cstdio>  
  2. #include<cstring>  
  3. #include<iostream>  
  4. #include<algorithm>  
  5. using namespace std;  
  6. const int MX = 205;  
  7.   
  8. struct Edge{  
  9.     int to,next;  
  10. };  
  11. Edge edge[MX * MX * 2];  
  12. int n,maps[MX][MX],tots = 0;  
  13. int head[MX * 5],now = 0,ans = 0,match[MX * 5];  
  14. bool vis[MX * 5];  
  15.   
  16. void adde(int u,int v){  
  17.     edge[++now].to = v;  
  18.     edge[now].next = head[u];  
  19.     head[u] = now;  
  20. }  
  21.   
  22. bool hungary(int u){  
  23.     for (int i = head[u];i;i = edge[i].next){  
  24.         int v = edge[i].to;  
  25.         if (!vis[v]){  
  26.             vis[v] = true;  
  27.             if (match[v] == 0 || hungary(match[v])){  
  28.                 match[v] = u;  
  29.                 return true;  
  30.             }  
  31.         }  
  32.     }  
  33.     return false;  
  34. }  
  35.   
  36. bool check(){                           //第一步贪心的预处理   
  37.     bool row = false,column[MX] = {0};      //这一步可以被省略掉,因为求最大匹配时已经包含了它   
  38.     for (int i = 1;i <= n;i++){              //实际上,因为它是必要条件   
  39.         row = false;                        //所以有了它可以增加严谨性,并不会影响答案   
  40.         for (int j = 1;j <= n;j++){          //这里可以选择把它注释掉   
  41.             if (maps[i][j]){  
  42.                 row = true;  
  43.                 column[j] = true;  
  44.             }  
  45.         }  
  46.         if (!row){  
  47.             return false;  
  48.         }  
  49.     }  
  50.     for (int i = 1;i <= n;i++){  
  51.         if (!column[i]) return false;  
  52.     }  
  53.     return true;  
  54. }  
  55.   
  56. void _init(){  
  57.     memset(maps,0,sizeof(maps));  
  58.     memset(head,0,sizeof(head));  
  59.     memset(edge,0,sizeof(edge));  
  60.     memset(match,0,sizeof(match));  
  61.     now = 0,ans = 0,tots = 0;  
  62. }  
  63.   
  64. int main(){  
  65.     int T;  
  66.     cin>>T;  
  67.     while (T–){  
  68.         scanf(”%d”,&n);  
  69.         for (int i = 1;i <= n;i++){  
  70.             for (int j = 1;j <= n;j++){  
  71.                 scanf(”%d”,&maps[i][j]);  
  72.                 if (maps[i][j] == 1){  
  73.                     ++tots;  
  74.                     adde(i,j + 210);        //建立二分图   
  75.                     adde(j + 210,i);  
  76.                 }  
  77.             }  
  78.         }  
  79.         for (int i = 1;i <= n;i++){  
  80.             memset(vis,0,sizeof(vis));  
  81.             if (hungary(i)) ++ans;  
  82.         }  
  83.         if (tots<n || !check() || ans<n){//1.总共 1 的数量小于 n;2.至少有一行或一列为白色   
  84.             printf(”No\n”);              //3.这个图不是完全匹配   
  85.         } else {                         //此时返回 No   
  86.             printf(”Yes\n”);             //上述条件都不满足,则返回 Yes   
  87.         }  
  88.         _init();  
  89.     }  
  90.     return 0;  
  91. }  
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值