题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2255
本来是用网络流来做这道题的,但是总是超时,搜索题解,都是用EK(匈牙利)来做的,索性又去学习二分图的最佳完美匹配。
看的刘汝佳的算法经典训练指南,只给了O(n^4)的代码,但好在能够学习算法,能够基本理解。后来从老师那里得到了二分图最佳完美匹配的通俗解释,如下:
有N个男人和N个女人。
其中男人都特有钱,能够出得起任何个女人的嫁妆。女的开始时都是身无分文。但是太有钱了女方又担心,担心男人有钱就会变坏,所以希望男的不能够太有钱,也不能够钱太少,女方此时定了一个苟刻的条件:如果某个男的和她财产之和要等于一个常量,由于每个女人对某个男的感觉不同,因而定的常量不同。
此时我们就可假设LXi (LXi = max(W ij)为第 i 个男人的财产,LYi (LYi = 0)为第i个女人的财产,W ij 为第 i 个 男人要娶第j个女人时两者的财产和常量。
第一个男的开始找起,他会先进行一次调查,判断所有女人之中是否会有合适的。如果不适合,他就会记下对方所需要的常量和自身的财产差值(这个差值肯定比0大,因为初始时的设定),如果找到了适合的了,那么两人牵手走人。如果这个男人调查一番都没有找到适合的,那么只能扔掉自己的一部分财产了,也不能够扔太多呀,之前不是调查过了麽,扔掉最少的差值 a (LXi -=a)。那么下轮调查很明显肯定会找到一个适合的。
调查过程有可能几个男满足同一个女的条件,那么此时先到先牵手。如果某个男的调查某个女的时候,发现满足该女的条件,不过这个女已经有男人了。该男人也不愿意轻意放过这个女人,就问一下这个女人是否肯抛弃她的男人跟他走。女的还是有点喜新厌旧的,她就问好的男人,说有人喜欢上她了,你可不可以另外找一个女人。女人的男人说,那我必须找到另外一个之后才可以跟你分。于是女人的男人去找一遍,如果还真是可以另外找到一个适合的,就果断的跟女人分了,如果没有找到,就两人继续在一起,不过为了不让女人受到这个男人的下一轮调查,给了自己的一些钱给自己的女人(LXi -= a, LYi += a)(男人之间喜欢吹牛,不知觉中把自己的调查结果说出来了,女人的男人给女人的钱刚好等于该男人准备要扔掉的钱)。
最后所有人都结成了对,且所有的对的财产加起来是最多的。
为什么呢?
由于每一对初始时都是 LXi +LYi >= W ij 。调查一轮还单身的男人扔掉了自己的一部分财产a,而扔掉的这一份a刚好是最少的,也就是说这个男人下一轮找到女人和他的财产和对于他来说是最高的。因而加起来是最优的。
看了这个,就明白多了。
动手敲代码,超时……
郁闷了好久,不知怎么下手改进,与学长探讨,果然发现了好多问题:
刘汝佳大神给的是O(n^4)的代码,于是改成了O(n^3)的,依旧超时……
后来发现,我是把n个人编号1~n,把n个房子编号n+1~2*n(以为人和房子的编号是不同的,后来才知道编号可以相同,可以通过顶标lx和ly来区分),这样点的范围就成了600,时间上升了很多倍,于是把房子的编号也改成了1~n,仍然超时……
对比题解代码,有发现了问题:大神的代码把修改顶标的过程做成了函数。如果对于点i不能找到匹配的话,就找一个最小的修改量进行修改,但是如果连修改量也找不到的话,刘的代码是在对这个点进行一轮的寻找,显然,仍然不会找到,就陷入了死循环;而题解上的处理方式是直接退出EK算法,这是一个大问题,于是照题解改代码,顽固超时……
后来,意识到在找最小修改量的时候,a的初始化的语句是这样的:a = (1<<31)-1;所以会重复计算,浪费时间,于是就定义INF,任性超时……
为什么?我不服,我把代码改乘和题解一样了,为啥还是超市?为什么?啊~~~~~
学长说:你把cin、cout改成scanf、printf试试。过了……
scanf、printf果然强大!
#include<iostream> #include<cstring> #include<cstdio> #define maxn 400 using namespace std; int W[maxn][maxn]; int lx[maxn],ly[maxn]; int lef[maxn]; int slack[maxn]; int INF = (1<<31)-1; bool S[maxn],T[maxn]; int n; bool match(int i) { S[i] = true; for (int j=1; j<=n; j++) { if (T[j]) continue; if (lx[i]+ly[j] == W[i][j]) { T[j] = true; if (!lef[j] || match(lef[j])) { lef[j] = i; return true; } } else slack[j] = min(slack[j],lx[i]+ly[j]-W[i][j]); } return false; } void KM() { for (int i=1; i<=n; i++){lef[i] = lx[i] = ly[i] = 0;for (int j=1; j<=n; j++)lx[i] = max(lx[i],W[i][j]);} for (int i=1; i<=n; i++) { for (int j=1; j<=n; j++) slack[j] = INF; for (;;) { for (int j=1; j<=n; j++) S[j] = T[j] =0; if (match(i)) break; int a = INF; for (int j=1; j<=n; j++) if (!T[j]) a = min(a,slack[j]); if (a == INF) return; for (int i=1; i<=n; i++) { if (S[i]) lx[i] -= a; if (T[i]) ly[i] += a; else slack[i] -= a; } } } } int main() { while (scanf("%d",&n)!=EOF) { for (int i=1; i<=n; i++)for (int j=1; j<=n; j++){scanf("%d",&W[i][j]);} KM(); int ans = 0; for (int i=1; i<=n; i++) ans += W[lef[i]][i]; printf("%d\n",ans); memset(W,0,sizeof(W)); } return 0; }