URAL-1584-Pharaohs’ Secrets【二分图最佳匹配】【好题】

When programmer Alex was in Egypt, he not only swam in the Red Sea and went sightseeing, but also studied history. When Alex visited the place where an archeological dig of an ancient temple was carried out, an excavation worker complained to him that they had to drag very heavy statues from place to place every day. This was because some Egyptologist had read in an ancient papyrus that if the statues were arranged in a special order, then some ancient hiding-place would open. When the temple had been dug out, these statues had stood as soldiers, forming a rectangle. Some statues were identical, so there were several types of statues. They were to be arranged into a rectangle of the same dimensions on the same place with all rows and columns symmetric with respect to their middles. This meant that the statues standing in the same row or column at equal distances to its ends had to be of the same type.
Alex offered his help. He wants to find the way to transform the rectangle into a symmetric one by means of the minimal number of moves.

Input
The first line contains the dimensions of the rectangle n and m (2 ≤ n, m ≤ 20). These integers are even. Each of the next n lines contains m lowercase English letters. Each letter denotes the type of the statue that stands in the rectangle at this position.

Output
Output the minimal number of statues that should be moved in order to make a symmetric rectangle. It is guaranteed that this is possible.

这里写图片描述

Notes
The arrangement in the example can be transformed to a symmetric one in only two moves: first the statue of the type x from the upper row should be moved to the place in the rightmost column where there is the statue of the type b, and this statue then should moved to the place where the first statue stood. After all moves each place must be occupied by exactly one statue, but during the moving process there can be several statues at the same place.

题目链接:URAL-1584

题目大意:给出n*m的矩阵,调整字母位置,使得最后形成的矩阵,每四个角字母都相同

例如样例:

第一次移动:
这里写图片描述

第二次移动:
这里写图片描述

题目思路:感觉很巧妙的二分图匹配。

事实上,因为他最后要形成每四角都相同的矩阵,我们也就是说只需要处理左上角矩阵即可。

c = n * m / 4 // 表示每四个角一个字母,总共需要c个字母,即每个字母应对应左上角一个方框

g[i][j] 表示: i : 左上角方框编号, j: 字母编号(c个)

权值:如果本来对应方框的字母相同权值+1(4个框),km求最大权,本来匹配的越大越好

答案: n * m - km()

以下是代码:

#include <iostream>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <functional>
#include <numeric>
#include <string>
#include <set>
#include <map>
#include <stack>
#include <vector>
#include <queue>
#include <deque>
#include <list>

using namespace std;
/* KM算法
 * 复杂度O(nx*nx*ny)
 * 求最大权匹配
 * 若求最小权匹配,可将权值取相反数,结果取相反数
 * 点的编号从0开始
 */
const int N = 505;
const int INF = 0x3f3f3f3f;
int nx,ny;//两边的点数 nx,第一队的个数,ny,第二队的个数。注意 nx <= ny,否则会死循环。
int g[N][N];//二分图描述  g[i][j],i属于第一队,j属于第二队。
int linker[N],lx[N],ly[N];//y中各点匹配状态,x,y中的点标号
int slack[N];
bool visx[N],visy[N];
bool DFS(int x)
{
    visx[x] = true;
    for(int y = 0; y < ny; y++)
    {
        if(visy[y])continue;
        int tmp = lx[x] + ly[y] - g[x][y];
        if(tmp == 0)
        {
            visy[y] = true;
            if(linker[y] == -1 || DFS(linker[y]))
            {
                linker[y] = x;
                return true;
            }
        }
        else if(slack[y] > tmp)
            slack[y] = tmp;
    }
    return false;
}
int KM()
{
    memset(linker,-1,sizeof(linker));
    memset(ly,0,sizeof(ly));
    for(int i = 0;i < nx;i++)
    {
        lx[i] = -INF;
        for(int j = 0;j < ny;j++)
            if(g[i][j] > lx[i])
                lx[i] = g[i][j];
    }
    for(int x = 0;x < nx;x++)
    {
        for(int i = 0;i < ny;i++)
            slack[i] = INF;
        while(true)
        {
            memset(visx,false,sizeof(visx));
            memset(visy,false,sizeof(visy));
            if(DFS(x))break;
            int d = INF;
            for(int i = 0;i < ny;i++)
                if(!visy[i] && d > slack[i])
                    d = slack[i];
            for(int i = 0;i < nx;i++)
                if(visx[i])
                    lx[i] -= d;
            for(int i = 0;i < ny;i++)
            {
                if(visy[i])ly[i] += d;
                else slack[i] -= d;
            }
        }
    }
    int res = 0;
    for(int i = 0;i < ny;i++)
        if(linker[i] != -1)
            res += g[linker[i]][i];
    return res;
}
//===
string s[30];
string ret;
int main()
{
    int n,m;
    cin >> n >> m;
    ret = "";
    for (int i = 0; i < n; i++)
    {
        cin >> s[i];
        ret += s[i];
    }

    sort(ret.begin(), ret.end());

    int c = n * m / 4; //左上角的点数
    nx = ny = c;

     for (int i = 0; i < n / 2; i++)  //遍历左上角矩阵,确定左上角其他一定能得和他一样
    {
        for (int j = 0; j < m / 2; j++)
        {
            int len = ret.size();
            for (int k = 0; k < len; k += 4)  //遍历存在的字母
            {
                int cnt = 0;   //km,最大权匹配
                if (s[i][j] == ret[k]) cnt++;
                if (s[i][m-j-1] == ret[k]) cnt++;
                if (s[n-i-1][j] == ret[k]) cnt++;
                if (s[n-i-1][m-j-1] == ret[k]) cnt++;

                g[i * m / 2 + j][k / 4] = cnt;
            }
        }
    }
    int ans = KM();
    cout << n * m - ans << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值