KM算法——带权二分图最佳匹配问题

来源:互联网 发布:最大公约数c语言算法 编辑:程序博客网 时间:2024/06/05 14:37

【参考】   我对KM算法的理解

一般对KM算法的描述,基本上可以概括成以下几个步骤: 
(1) 初始化可行标杆 
(2) 用匈牙利算法寻找完备匹配 
(3) 若未找到完备匹配则修改可行标杆 
(4) 重复(2)(3)直到找到相等子图的完备匹配 

关于该算法的流程及实施,网上有很多介绍,基本上都是围绕可行标杆如何修改而进行的讨论,至于原理并没有给出深入的探讨。 

KM算法是用于寻找带权二分图最佳匹配的算法。 

二分图是这样一种图:所有顶点可以分成两个集:X和Y,其中X和Y中的任意两个在同一个集中的点都不相连,而来自X集的顶点与来自Y集的顶点有连线。当这些连线被赋于一定的权重时,这样的二分图便是带权二分图。 

二分图匹配是指求出一组边,其中的顶点分别在两个集合中,且任意两条边都没有相同的顶点,这组边叫做二分图的匹配,而所能得到的最大的边的个数,叫做二分图的最大匹配。 

我们也可以换个角度看二分图的最大匹配,即二分图的每条边的默认权重为1,我们求到的二分图的最大匹配的权重最大。对于带权二分图,其边有大于0的权重,找到一组匹配,使其权重最大,即为带权二分图的最佳匹配。 

匈牙利算法一般用于寻找二分图的最大匹配。算法根据一定的规则选择二分图的边加入匹配子图中,其基本模式为: 

初始化匹配子图为空 
While 找得到增广路径 
Do 把增广路径添加到匹配子图中 

增广路径有如下特性: 
1. 有奇数条边 
2. 起点在二分图的X边,终点在二分图的Y边 
3. 路径上的点一定是一个在X边,一个在Y边,交错出现。 
4. 整条路径上没有重复的点 
5. 起点和终点都是目前还没有配对的点,其他的点都已经出现在匹配子图中 
6. 路径上的所有第奇数条边都是目前还没有进入目前的匹配子图的边,而所有第偶数条边都已经进入目前的匹配子图。奇数边比偶数边多一条边 
7. 于是当我们把所有第奇数条边都加到匹配子图并把条偶数条边都删除,匹配数增加了1. 

例如下图,蓝色的是当前的匹配子图,目前只有边x0y0,然后通过x1找到了增广路径:x1y0->y0x0->x0y2 

 

其中第奇数第边x1y0和x0y2不在当前的匹配子图中,而第偶数条边x0y0在匹配子图中,通过添加x1y0和x0y2到匹配子图并删除x0y0,使得匹配数由1增加到了2。每找到一条增广路径,通过添加删除边,我们总是能使匹配数加1. 

增广路径有两种寻径方法,一个是深搜,一个是宽搜。例如从x2出发寻找增广路径,如果是深搜,x2找到y0匹配,但发现y0已经被x1匹配了,于是就深入到x1,去为x1找新的匹配节点,结果发现x1没有其他的匹配节点,于是匹配失败,x2接着找y1,发现y1可以匹配,于是就找到了新的增广路径。如果是宽搜,x1找到y0节点的时候,由于不能马上得到一个合法的匹配,于是将它做为候选项放入队列中,并接着找y1,由于y1已经匹配,于是匹配成功返回了。相对来说,深搜要容易理解些,其栈可以由递归过程来维护,而宽搜则需要自己维护一个队列,并对一路过来的路线自己做标记,实现起来比较麻烦。 

对于带权重的二分图来说,我们可以把它看成一个所有X集合的顶点到所有Y集合的顶点均有边的二分图(把原来没有的边添加入二分图,权重为0即可),也就是说它必定存在完备匹配(即其匹配数为min(|X|,|Y|))。为了使权重达到最大,我们实际上是通过贪心算法来选边,形成一个新的二分图(我们下面叫它二分子图好了),并在该二分图的基础上寻找最大匹配,当该最大匹配为完备匹配时,我们可以确定该匹配为最佳匹配。(在这里我们如此定义最大匹配:匹配边数最多的匹配和最佳匹配:匹配边的权重和最大的匹配。) 

贪心算法总是将最优的边优先加入二分子图,该最优的边将对当前的匹配子图带来最大的贡献,贡献的衡量是通过标杆来实现的。下面我们将通过一个实例来解释这个过程。 

有带权二分图: 

 
算法把权重转换成标杆,X集跟Y集的每个顶点各有一个标杆值,初始情况下权重全部放在X集上。由于每个顶点都将至少会有一个匹配点,贪心算法必然优先选择该顶点上权重最大的边(最理想的情况下,这些边正好没有交点,于是我们自然得到了最佳匹配)。最初的二分子图为:(可以看到初始化时X标杆为该顶点上的最大权重,而Y标杆为0) 

 
从X0找增广路径,找到X0Y4;从X1找不到增广路径,也就是说,必须往二分子图里边添加新的边,使得X1能找到它的匹配,同时使权重总和添加最大。由于X1通往Y4而Y4已经被X0匹配,所以有两种可能,一个是为X0找一个新的匹配点并把Y4让给X1,或者是为X1找一个新的匹配点,现在我们将要看到标杆的作用了。根据传统的算法描述,能够进入二分子图的边的条件为L(x)+L(y)>=weight(xy)。当找不到增广路径时,对于搜索过的路径上的XY点,设该路径上的X顶点集为S,Y顶点集为T,对所有在S中的点xi及不在T中的点yj,计算d=min{(L(xi)+L(yj)-weight(xiyj))},从S集中的X标杆中减去d,并将其加入到T集中的Y的标杆中,由于S集中的X标杆减少了,而不在T中的Y标杆不变,相当于这两个集合中的L(x)+L(y)变小了,也就是,有新的边可以加入二分子图了。从贪心选边的角度看,我们可以为X0选择新的边而抛弃原先的二分子图中的匹配边,也可以为X1选择新的边而抛弃原先的二分子图中的匹配边,因为我们不能同时选择X0Y4和X1Y4,因为这是一个不合法匹配,这个时候,d=min{(L(xi)+L(yj)-weight(xiyj))}的意义就在于,我们选择一条新的边,这条边将被加入匹配子图中使得匹配合法,选择这条边形成的匹配子图,将比原先的匹配子图加上这条非法边组成的非法匹配子图的权重和(如果它是合法的,它将是最大的)小最少,即权重最大了。好绕口的。用数学的方式表达,设原先的不合法匹配(它的权重最大,因为我们总是从权重最大的边找起的)的权重为W,新的合法匹配为W’,d为min{W-W’i}。在这个例子中,S={X0, X1},Y={Y4},求出最小值d=L(X1)+L(Y0)-weight(X1Y0)=2,得到新的二分子图: 

 
重新为X1寻找增广路径,找到X1Y0,可以看到新的匹配子图的权重为9+6=15,比原先的不合法的匹配的权重9+8=17正好少d=2。 
接下来从X2出发找不到增广路径,其走过的路径如蓝色的路线所示。形成的非法匹配子图:X0Y4,X1Y0及X2Y0的权重和为22。在这条路径上,只要为S={X0,X1,X2}中的任意一个顶点找到新的匹配,就可以解决这个问题,于是又开始求d。 
d=L(X0)+L(Y2)-weight(X0Y2)=L(X2)+L(Y1)-weight(X2Y1)=1. 
新的二分子图为: 

 

重新为X2寻找增广路径,如果我们使用的是深搜,会得到路径:X2Y0->Y0X1->X1Y4->Y4X0->X0Y2,即奇数条边而删除偶数条边,新的匹配子图中由这几个顶点得到的新的权重为21;如果使用的是宽搜,会得到路径X2Y1,另上原先的两条匹配边,权重为21。假设我们使用的是宽搜,得到的新的匹配子图为: 

 
接下来依次类推,直到为X4找到一个匹配点。 

KM算法的最大特点在于利用标杆和权重来生成一个二分子图,在该二分子图上面找最大匹配,而且,当些仅当找到完备匹配,才能得到最佳匹配。标杆和权重的作用在于限制新边的加入,使得加入的新边总是能为子图添加匹配数,同时又令权重和得到最大的提高。 

下面是匈牙利算法的dfs和bfs实现,是用c++实现的: 
Cpp代码 
  1. //---------------------DFS---------------------------------  
  2. #include<iostream>  
  3. #include<memory.h>  
  4. using namespace std;  
  5.   
  6. #define MAXN 10  
  7. int graph[MAXN][MAXN];  
  8. int match[MAXN];  
  9. int visitX[MAXN], visitY[MAXN];  
  10. int nx, ny;  
  11.   
  12. bool findPath( int u )  
  13. {  
  14.     visitX[u] = 1;  
  15.     forint v=0; v<ny; v++ )  
  16.     {  
  17.         if( !visitY[v] && graph[u][v] )  
  18.         {  
  19.             visitY[v] = 1;  
  20.             if( match[v] == -1 || findPath(match[v]) )  
  21.             {  
  22.                 match[v] = u;  
  23.                 return true;  
  24.             }  
  25.         }  
  26.     }  
  27.     return false;  
  28. }  
  29.   
  30. int dfsHungarian()  
  31. {  
  32.     int res = 0;  
  33.     memset( match, -1, sizeof(match) );  
  34.     forint i=0; i<nx; i++ )  
  35.     {  
  36.         memset( visitX, 0, sizeof(visitX) );  
  37.         memset( visitY, 0, sizeof(visitY) );  
  38.         if( findPath(i) )  
  39.             res++;  
  40.     }  
  41.     return res;  
  42. }  
  43.   
  44. //-----------------------------BFS-------------------------------  
  45. #include<iostream>  
  46. #include<memory.h>  
  47. using namespace std;  
  48.   
  49. #define MAXN 10  
  50. int graph[MAXN][MAXN];  
  51. //在bfs中,增广路径的搜索是一层一层展开的,所以必须通过prevX来记录上一层的顶点  
  52. //chkY用于标记某个Y顶点是否被目前的X顶点访问尝试过。  
  53. int matchX[MAXN], matchY[MAXN], prevX[MAXN], chkY[MAXN];  
  54. int queue[MAXN];  
  55. int nx, ny;  
  56.   
  57. int bfsHungarian()  
  58. {  
  59.     int res = 0;  
  60.     int qs, qe;  
  61.     memset( matchX, -1, sizeof(matchX) );  
  62.     memset( matchY, -1, sizeof(matchY) );  
  63.     memset( chkY, -1, sizeof(chkY) );  
  64.   
  65.     forint i=0; i<nx; i++ )  
  66.     {  
  67.         if( matchX[i] == -1 )   //如果该X顶点未找到匹配点,将其放入队列。  
  68.         {  
  69.             qs = qe = 0;  
  70.             queue[qe++] = i;  
  71.             prevX[i] = -1;  //并且标记,它是路径的起点  
  72.             bool flag = 0;  
  73.   
  74.             while( qs<qe && !flag )  
  75.             {  
  76.                 int u = queue[qs];  
  77.                 forint v=0; v<ny&&!flag; v++ )  
  78.                 {  
  79.                     if( graph[u][v] && chkY[v]!=i ) //如果该节点与u有边且未被访问过  
  80.                     {  
  81.                         chkY[v] = i;    //标记且将它的前一个顶点放入队列中,也就是下次可能尝试这个顶点看能否为它找到新的节点  
  82.                         queue[qe++] = matchY[v];  
  83.                         if( matchY[v] >= 0 )  
  84.                             prevX[matchY[v]] = u;  
  85.                         else    //到达了增广路径的最后一站  
  86.                         {  
  87.                             flag = 1;  
  88.                             int d=u, e=v;  
  89.                             while( d!=-1 )  //一路通过prevX找到路径的起点  
  90.                             {  
  91.                                 int t = matchX[d];  
  92.                                 matchX[d] = e;  
  93.                                 matchY[e] = d;  
  94.                                 d = prevX[d];  
  95.                                 e = t;  
  96.                             }  
  97.                         }  
  98.                     }  
  99.                 }  
  100.                 qs++;  
  101.             }  
  102.             if( matchX[i] != -1 )  
  103.                 res++;  
  104.         }  
  105.     }  
  106.     return res;  
  107. }


模板:(O  ^ 4) 

复制代码
#define M 505
#define inf 0x3fffffff
bool sx[M], sy[M];
int match[M], w[M][M], n, m, d, lx[M], ly[M];
//n:左集元素个数; m:右集元素个数
void init ()
{
memset (w, 0, sizeof(w)); //不一定要,求最小值一般要初始化为负无穷!
}

bool dfs (int u)
{
int v; sx[u] = true;
for (v = 0; v < m; v++)
{
if (!sy[v] && lx[u]+ly[v]==w[u][v])
{
sy[v] = true;
if (match[v] == -1 || dfs (match[v]))
{
match[v] = u;
return true;
}
}
}
return false;
}

int KM ()
{
int i, j, k, sum = 0;
memset (ly, 0, sizeof(ly));
for (i = 0; i < n; i++)
{
lx[i] = -inf;
for (j = 0; j < m; j++)
if (lx[i] < w[i][j])
lx[i] = w[i][j];
}
memset (match, -1, sizeof(match));
for (i = 0; i < n; i++)
{
while (1)
{
memset (sx, false, sizeof(sx));
memset (sy, false, sizeof(sy));
if (dfs (i))
break;
d = inf;
for (j = 0; j < n; j++)
if (sx[j])
for (k = 0; k < m; k++)
if (!sy[k])
d = min (d, lx[j]+ly[k]-w[j][k]);
if (d == inf) //找不到完美匹配
return -1;
for (j = 0; j < n; j++)
if (sx[j])
lx[j] -= d;
for (j = 0; j < m; j++)
if (sy[j])
ly[j] += d;
}
}
for (i = 0; i < m; i++)
if (match[i] > -1)
sum += w[match[i]][i];
return sum;
}
复制代码

改进后的模板(O^3)

复制代码
/*其实在求最大 最小的时候只要用一个模板就行了,把边的权值去相反数即可得到另外一个.求结果的时候再去相反数即可*/
/*最大最小有一些地方不同。。*/
#include <iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
//赤裸裸的模板啊。。
const int maxn = 101;
const int INF = (1<<31)-1;
int w[maxn][maxn];
int lx[maxn],ly[maxn]; //顶标
int linky[maxn];
int visx[maxn],visy[maxn];
int slack[maxn];
int nx,ny;
bool find(int x)
{
visx[x] = true;
for(int y = 0; y < ny; y++)
{
if(visy[y])
continue;
int t = lx[x] + ly[y] - w[x][y];
if(t==0)
{
visy[y] = true;
if(linky[y]==-1 || find(linky[y]))
{
linky[y] = x;
return true; //找到增广轨
}
}
else if(slack[y] > t)
slack[y] = t;
}
return false; //没有找到增广轨(说明顶点x没有对应的匹配,与完备匹配(相等子图的完备匹配)不符)
}

int KM() //返回最优匹配的值
{
int i,j;

memset(linky,-1,sizeof(linky));
memset(ly,0,sizeof(ly));
for(i = 0; i < nx; i++)
for(j = 0,lx[i] = -INF; j < ny; j++)
if(w[i][j] > lx[i])
lx[i] = w[i][j];
for(int x = 0; x < nx; x++)
{
for(i = 0; i < ny; i++)
slack[i] = INF;
while(true)
{
memset(visx,0,sizeof(visx));
memset(visy,0,sizeof(visy));
if(find(x)) //找到增广轨,退出
break;
int d = INF;
for(i = 0; i < ny; i++) //没找到,对l做调整(这会增加相等子图的边),重新找
{
if(!visy[i] && d > slack[i])
d = slack[i];
}
for(i = 0; i < nx; i++)
{
if(visx[i])
lx[i] -= d;
}
for(i = 0; i < ny; i++)
{
if(visy[i])
ly[i] += d;
else
slack[i] -= d;
}
}
}
int result = 0;
for(i = 0; i < ny; i++)
if(linky[i]>-1)
result += w[linky[i]][i];
return result;
}

int main()
{
// freopen("g:/1.txt","r",stdin);
while(true)
{
scanf("%d%d",&nx,&ny);
int a,b,c;
while(scanf("%d%d%d",&a,&b,&c),a+b+c)
{
w[a][b]=c;
}
printf("%d\n",KM());
break;
}
return 0;
}
复制代码

【KM算法求二分图的最佳匹配思想】
对于具有二部划分( V1, V2 )的加权完全二分图,其中 V1= { x1, x2, x3, ... , xn }, V2= { y1, y2, y3, ... , yn },边< xi, yj >具有权值 Wi,j 。该带权二分图中一个总权值最大的完美匹配,称之为最佳匹配。
 
记 L(x) 表示结点 x 的标记量,如果对于二部图中的任何边<x,y>,都有 L(x)+ L(y)>= Wx,y,我们称 L 为二部图的可行顶标。
设 G(V,E) 为二部图, G'(V,E') 为二部图的子图。如果对于 G' 中的任何边<x,y> 满足, L(x)+ L(y)== Wx,y,我们称 G'(V,E') 为 G(V,E) 的等价子图。
 
定理一:设 L 是二部图 G 的可行顶标。若 L 等价子图 G有完美匹配 M,则 M 是 G 的最佳匹配。
证明:由于 GL 是 G 的等价子图,M 是 GL 的完美匹配,所以,M 也是 G  的完美匹配。以由于对于匹配 M 的每条边 e ,都有 e∈ E( GL ),而且 M 中每条边覆盖每个顶点正好一次,所以
W( M )= å W(e), e∈ M = å L(x), x∈ V
另一方面,对于 G 的任何完美匹配 M' 有
W( M' )= å W(e), e∈ M' <= å L(x), x∈ V
于是 W( M )>= W( M' ),即 M 是 G 的最优匹配。
 
由上述定理,我们可以通过来不断修改可行顶标,得到等价子图,从而求出最佳匹配。
就像匈牙利算法一样,我们依次为每一个顶点 i 寻找增广路径,如果寻找增广路径失败,我们就修改相应的可行顶标,来得到增广路径。
如图:
 1  2  3  |
 3  2  4  |
 2  3  5  |
若要对这个完全二分图求最佳匹配
 
初始化:
Lx(1)= max{ y| w(1,y), 1<= y<= 3 }= max{ 1, 2, 3 }= 3, Ly(1)= 0
Lx(2)= max{ 3, 2, 4 }= 4, Ly(2)= 0
Lx(3)= max{ 2, 3, 5 }= 5, Ly(3)= 0;
我们建立等价子图( 满足 Lx(x)+ Ly(y)== W(x,y) ) 如下:
km算法求二分图最佳匹配
对于该图,运用匈牙利算法对 X 部顶点 1 求增广路径,得到一个匹配,如图( 红色代表匹配边 ):km算法求二分图最佳匹配
 对 X 部顶点 2 求增广路径失败,寻找增广路径的过程为 X 2-> Y 3-> X 1。我们把寻找增广路径失败的 DFS 的交错树中,在 X 部顶点集称之为 S, 在 Y 部的顶点集称之为 T。则 S= { 1, 2 },T= { 3 }。现在我们就通过修改顶标值来扩大等价子图,如何修改。
 
1)   我们寻找一个 d 值,使得 d= min{ (x,y)| Lx(x)+ Ly(y)- W(x,y), x∈ S, y∉ T },因些,这时 d= min{
Lx(1)+Ly(1)-W(1,1),  Lx(1)+Ly(2)-W(1,2),  Lx(2)+Ly(1)-W(2,1),  Lx(2)+Ly(2)-W(2,2) }=
min{ 3+0- 1, 3+0-2,  4+0-3,  4+0-2 }= min{ 2, 1, 1, 2 }= 1。
寻找最小的 d 是为了保证修改后仍满足性质对于边 <x,y> 有 Lx(x)+ Ly(y)>= W(x,y)。
 
2)   然后对于顶点 x
1. 如果 x∈ S 则 Lx(x)= Lx(x)- d。
2. 如果 x∈ T 则 Ly(x)= Ly(x)+ d。
3. 其它情况保持不变。
如此修改后,我们发现对于边<x,y>,顶标 Lx(x)+ Ly(y) 的值为
1.  Lx(x)- d+ Ly(y)+ d,  x∈ S, y∈ T。
2.  Lx(x)+ Ly(y),  x∉ S,  y∉ T。
3.  Lx(x)- d+ Ly(y), x∈ S, y∉ T。
4.  Lx(x)+ Ly(y)+ d, x∉ S,  y∈ T。
易知,修改后对于任何边仍满足 Lx(x)+ Ly(y)>= W(x,y),并且第三种情况顶标值减少了 d,如此定会使等价子图扩大。
 
就上例而言: 修改后 Lx(1)= 2, Lx(2)= 3, Lx(3)= 5, Ly(1)= 0, Ly(1)= 0, Ly(2)= 0, Ly(3)= 1。
这时 Lx(2)+Ly(1)=3+0=3= W(2,1),在等价子图中增加了一条边,等价子图变为:
 km算法求二分图最佳匹配
如此按以上方法,得到等价子图的完美匹配。
 
另外计算 d 值的时候可以进行一些优化。
定义 slack(y)= min{ (x,y)| Lx(x)+ Ly(y)- W(x,y),x∈ S,  y∉ T }
这样能在寻找增广路径的时候就顺便将 slack 求出。
(以上为摘上网络)

【KM算法及其具体过程】
(1)可行点标:每个点有一个标号,记lx[i]为X方点i的标号,ly[j]为Y方点j的标号。如果对于图中的任意边(i, j, W)都有lx[i]+ly[j]>=W,则这一组点标是可行的。特别地,对于lx[i]+ly[j]=W的边(i, j, W),称为可行边
(2)KM 算法的核心思想就是通过修改某些点的标号(但要满足点标始终是可行的),不断增加图中的可行边总数,直到图中存在仅由可行边组成的完全匹配为止,此时这个 匹配一定是最佳的(因为由可行点标的的定义,图中的任意一个完全匹配,其边权总和均不大于所有点的标号之和,而仅由可行边组成的完全匹配的边权总和等于所 有点的标号之和,故这个匹配是最佳的)。一开始,求出每个点的初始标号:lx[i]=max{e.W|e.x=i}(即每个X方点的初始标号为与这个X方 点相关联的权值最大的边的权值),ly[j]=0(即每个Y方点的初始标号为0)。这个初始点标显然是可行的,并且,与任意一个X方点关联的边中至少有一条可行边
(3)然后,从每个X方点开始DFS增广。DFS增广的过程与最大匹配的Hungary算法基本相同,只是要注意两点:一是只找可行边,二是要把搜索过程中遍历到的X方点全部记下来(可以用vst搞一下),以进行后面的修改;
(4) 增广的结果有两种:若成功(找到了增广轨),则该点增广完成,进入下一个点的增广。若失败(没有找到增广轨),则需要改变一些点的标号,使得图中可行边的 数量增加。方法为:将所有在增广轨中(就是在增广过程中遍历到)的X方点的标号全部减去一个常数d,所有在增广轨中的Y方点的标号全部加上一个常数d,则 对于图中的任意一条边(i, j, W)(i为X方点,j为Y方点):
<1>i和j都在增广轨中:此时边(i, j)的(lx[i]+ly[j])值不变,也就是这条边的可行性不变(原来是可行边则现在仍是,原来不是则现在仍不是);
<2>i在增广轨中而j不在:此时边(i, j)的(lx[i]+ly[j])的值减少了d,也就是原来这条边不是可行边(否则j就会被遍历到了),而现在可能是;
<3>j在增广轨中而i不在:此时边(i, j)的(lx[i]+ly[j])的值增加了d,也就是原来这条边不是可行边(若这条边是可行边,则在遍历到j时会紧接着执行DFS(i),此时i就会被遍历到),现在仍不是;
<4>i和j都不在增广轨中:此时边(i, j)的(lx[i]+ly[j])值不变,也就是这条边的可行性不变。
这 样,在进行了这一步修改操作后,图中原来的可行边仍可行,而原来不可行的边现在则可能变为可行边。那么d的值应取多少?显然,整个点标不能失去可行性,也 就是对于上述的第<2>类边,其lx[i]+ly[j]>=W这一性质不能被改变,故取所有第<2>类边的 (lx[i]+ly[j]-W)的最小值作为d值即可。这样一方面可以保证点标的可行性,另一方面,经过这一步后,图中至少会增加一条可行边。
(5)修改后,继续对这个X方点DFS增广,若还失败则继续修改,直到成功为止;
(6)以上就是KM算法的基本思路。但是朴素的实现方法,时间复杂度为O(n4)——需要找O(n)次增广路,每次增广最多需要修改O(n)次顶标,每次修改顶 标时由于要枚举边来求d值,复杂度为O(n2)。实际上KM算法的复杂度是可以做到O(n3)的。我们给每个Y顶点一个“松弛量”函数slack,每次开 始找增广路时初始化为无穷大。在寻找增广路的过程中,检查边(i,j)时,如果它不在相等子图中,则让slack[j]变成原值与 A[i]+B[j]-w[i,j]的较小值。这样,在修改顶标时,取所有不在交错树中的Y顶点的slack值中的最小值作为d值即可。但还要注意一点:修 改顶标后,要把所有不在交错树中的Y顶点的slack值都减去d。

【求二分图的最小匹配】
只需把权值取反,变为负的,再用KM算出最大权匹配,取反则为其最小权匹配。


题目推荐:

第一题:hdu  2255   奔小康赚大钱

http://acm.hdu.edu.cn/showproblem.php?pid=2255

模板不解释

复制代码/*其实在求最大 最小的时候只要用一个模板就行了,把边的权值去相反数即可得到另外一个.求结果的时候再去相反数即可*//*最大最小有一些地方不同。。*/#include <iostream>#include<cstring>#include<cstdio>#include<cmath>//赤裸裸的模板啊。。const int maxn = 301;const int INF = (1<<31)-1;int w[maxn][maxn];int lx[maxn],ly[maxn]; //顶标int linky[maxn];int visx[maxn],visy[maxn];int slack[maxn];int n;bool find(int x){    visx[x] = true;    for(int y = 1; y <=n; y++)    {        if(visy[y])            continue;        int t = lx[x] + ly[y] - w[x][y];        if(t==0)        {            visy[y] = true;            if(linky[y]==-1 || find(linky[y]))            {                linky[y] = x;                return true;        //找到增广轨            }        }        else if(slack[y] > t)            slack[y] = t;    }    return false;                   //没有找到增广轨(说明顶点x没有对应的匹配,与完备匹配(相等子图的完备匹配)不符)}int KM()                //返回最优匹配的值{    int i,j;    memset(linky,-1,sizeof(linky));    memset(ly,0,sizeof(ly));    for(i = 1; i <=n; i++)    {        lx[i] = -INF;         for(j = 1; j <=n; j++)            if(w[i][j] > lx[i])                lx[i] = w[i][j];    }    for(int x = 1; x <=n; x++)    {        for(i = 1; i <=n; i++)            slack[i] = INF;        while(true)        {            memset(visx,0,sizeof(visx));            memset(visy,0,sizeof(visy));            if(find(x))                     //找到增广轨,退出                break;            int d = INF;            for(i = 1; i <=n; i++)          //没找到,对l做调整(这会增加相等子图的边),重新找            {                if(!visy[i] && d > slack[i])                    d = slack[i];            }            for(i = 1; i <=n; i++)            {                if(visx[i])                    lx[i] -= d;            }            for(i = 1; i <=n; i++)            {                if(visy[i])                     ly[i] += d;                else                    slack[i] -= d;            }        }    }    int result = 0;    for(i = 1; i <=n; i++)    if(linky[i]>-1)        result += w[linky[i]][i];    return result;}int main(){   // freopen("g:/1.txt","r",stdin);    while(scanf("%d",&n)==1)    {        int cost;        for(int i=1;i<=n;i++)        for(int j=1;j<=n;j++)        {            scanf("%d",&cost);            w[i][j]=cost;        }        printf("%d\n",KM());    }    return 0;}复制代码

第二题:  hdu 1533  Going Home

http://acm.hdu.edu.cn/showproblem.php?pid=1533

 用 w[i][j] = -w[i][j]建图再套模板 求最大值   输出【-sum】

/*其实在求最大 最小的时候只要用一个模板就行了,把边的权值去相反数即可得到另外一个.求结果的时候再去相反数即可*//*最大最小有一些地方不同。。*/#include <iostream>#include<cstring>#include<cstdio>#include<cmath>int fx,fy;const int maxn = 301;const int INF = 0xffffff;struct node{    int x,y;}sx[maxn],sy[maxn];int w[maxn][maxn];int lx[maxn],ly[maxn]; //顶标int linky[maxn];int visx[maxn],visy[maxn];int slack[maxn];bool find(int x){    visx[x] = true;    for(int y = 1; y <=fy; y++)    {        if(visy[y])            continue;        int t = lx[x] + ly[y] - w[x][y];        if(t==0)        {            visy[y] = true;            if(linky[y]==-1 || find(linky[y]))            {                linky[y] = x;                return true;        //找到增广轨            }        }        else if(slack[y] > t)            slack[y] = t;    }    return false;                   //没有找到增广轨(说明顶点x没有对应的匹配,与完备匹配(相等子图的完备匹配)不符)}int KM()                //返回最优匹配的值{    int i,j;    memset(linky,-1,sizeof(linky));    memset(ly,0,sizeof(ly));    for(i = 1; i <=fx; i++)    {        lx[i] = -INF;         for(j = 1; j <=fy; j++)            if(w[i][j] > lx[i])                lx[i] = w[i][j];    }    for(int x = 1; x <=fx; x++)    {        for(i = 1; i <=fy; i++)            slack[i] = INF;        while(true)        {            memset(visx,0,sizeof(visx));            memset(visy,0,sizeof(visy));            if(find(x))                     //找到增广轨,退出                break;            int d = INF;            for(i = 1; i <=fy; i++)          //没找到,对l做调整(这会增加相等子图的边),重新找            {                if(!visy[i] && d > slack[i])                    d = slack[i];            }            for(i = 1; i <=fx; i++)            {                if(visx[i])                    lx[i] -= d;            }            for(i = 1; i <=fy; i++)            {                if(visy[i])                     ly[i] += d;                else                    slack[i] -= d;            }        }    }    int result = 0;    for(i = 1; i <=fy; i++)    if(linky[i]>-1)        result += w[linky[i]][i];    return result;}int main(){   // freopen("g:/1.txt","r",stdin);    int n,m;    while(scanf("%d%d",&n,&m),n+m)    {        char cost[maxn];        int i,j;fx=0,fy=0;        for( i=0;i<n;i++)        {             scanf("%s",cost);             for(int j=0;j<m;j++)             if(cost[j]=='m')             sx[++fx].x=i,sx[fx].y=j;             else if(cost[j]=='H')             sy[++fy].x=i,sy[fy].y=j;        }        for(i=1;i<=fx;i++)        {           for(j=1;j<=fy;j++)           {              int xx=fabs(sx[i].y-sy[j].y)+fabs(sx[i].x-sy[j].x);              w[i][j]=-xx;           }         }        printf("%d\n",-KM());    }    return 0;}

第三题:  hdu  1853  Cyclic Tour

注意是有向图,和重边的判断

/*其实在求最大 最小的时候只要用一个模板就行了,把边的权值去相反数即可得到另外一个.求结果的时候再去相反数即可*//*最大最小有一些地方不同。。*/#include <iostream>#include<cstring>#include<cstdio>#include<cmath>int n,m;const int maxn = 105;const int INF = 0xffffff;int w[maxn][maxn];int lx[maxn],ly[maxn]; //顶标int linky[maxn];int visx[maxn],visy[maxn];int slack[maxn];bool find(int x){    visx[x] = true;    for(int y = 1; y <=n; y++)    {        if(visy[y])            continue;        int t = lx[x] + ly[y] - w[x][y];        if(t==0)        {            visy[y] = true;            if(linky[y]==-1 || find(linky[y]))            {                linky[y] = x;                return true;        //找到增广轨            }        }        else if(slack[y] > t)            slack[y] = t;    }    return false;                   //没有找到增广轨(说明顶点x没有对应的匹配,与完备匹配(相等子图的完备匹配)不符)}int KM()                //返回最优匹配的值{    int i,j;    memset(linky,-1,sizeof(linky));    memset(ly,0,sizeof(ly));    for(i = 1; i <=n; i++)    {        lx[i] = -INF;         for(j = 1; j <=n; j++)            if(w[i][j] > lx[i])                lx[i] = w[i][j];    }    for(int x = 1; x <=n; x++)    {        for(i = 1; i <=n; i++)            slack[i] = INF;        while(true)        {            memset(visx,0,sizeof(visx));            memset(visy,0,sizeof(visy));            if(find(x))                     //找到增广轨,退出                break;            int d = INF;            for(i = 1; i <=n; i++)          //没找到,对l做调整(这会增加相等子图的边),重新找            {                if(!visy[i] && d > slack[i])                    d = slack[i];            }            for(i = 1; i <=n; i++)            {                if(visx[i])                    lx[i] -= d;            }            for(i = 1; i <=n; i++)            {                if(visy[i])                     ly[i] += d;                else                    slack[i] -= d;            }        }    }    int result = 0 ;    for(i = 1; i <=n; i++)    {        if(linky[i]==-1||w[linky[i]][i]==-INF)        return 1;        else    result += w[linky[i]][i];    }    return result;}int main(){   // freopen("g:/1.txt","r",stdin);    while(scanf("%d%d",&n,&m)==2)    {        for(int i=1;i<=n;i++)        {            for(int j=1;j<=n;j++)            w[i][j]=-INF;        }        for(int i=1;i<=m;i++)        {           int a,b,c;           scanf("%d%d%d",&a,&b,&c);           if(-c>w[a][b])           w[a][b]=-c;        }        printf("%d\n",-KM());    }    return 0;}

第四题:hdu  3488  Tour

http://acm.hdu.edu.cn/showproblem.php?pid=3488 
跟第三题几乎一样


/*其实在求最大 最小的时候只要用一个模板就行了,把边的权值去相反数即可得到另外一个.求结果的时候再去相反数即可*//*最大最小有一些地方不同。。*/#include <iostream>#include<cstring>#include<cstdio>#include<cmath>int n,m;const int maxn = 205;const int INF = 0xffffff;int w[maxn][maxn];int lx[maxn],ly[maxn]; //顶标int linky[maxn];int visx[maxn],visy[maxn];int slack[maxn];bool find(int x){    visx[x] = true;    for(int y = 1; y <=n; y++)    {        if(visy[y])            continue;        int t = lx[x] + ly[y] - w[x][y];        if(t==0)        {            visy[y] = true;            if(linky[y]==-1 || find(linky[y]))            {                linky[y] = x;                return true;        //找到增广轨            }        }        else if(slack[y] > t)            slack[y] = t;    }    return false;                   //没有找到增广轨(说明顶点x没有对应的匹配,与完备匹配(相等子图的完备匹配)不符)}int KM()                //返回最优匹配的值{    int i,j;    memset(linky,-1,sizeof(linky));    memset(ly,0,sizeof(ly));    for(i = 1; i <=n; i++)    {        lx[i] = -INF;         for(j = 1; j <=n; j++)            if(w[i][j] > lx[i])                lx[i] = w[i][j];    }    for(int x = 1; x <=n; x++)    {        for(i = 1; i <=n; i++)            slack[i] = INF;        while(true)        {            memset(visx,0,sizeof(visx));            memset(visy,0,sizeof(visy));            if(find(x))                     //找到增广轨,退出                break;            int d = INF;            for(i = 1; i <=n; i++)          //没找到,对l做调整(这会增加相等子图的边),重新找            {                if(!visy[i] && d > slack[i])                    d = slack[i];            }            for(i = 1; i <=n; i++)            {                if(visx[i])                    lx[i] -= d;            }            for(i = 1; i <=n; i++)            {                if(visy[i])                     ly[i] += d;                else                    slack[i] -= d;            }        }    }    int result = 0 ;    for(i = 1; i <=n; i++)    {        if(linky[i]==-1||w[linky[i]][i]==-INF)        return 1;        else    result += w[linky[i]][i];    }    return result;}int main(){   // freopen("g:/1.txt","r",stdin);    int cas; scanf("%d",&cas);    while(cas--)    {       scanf("%d%d",&n,&m);        for(int i=1;i<=n;i++)        {            for(int j=1;j<=n;j++)            w[i][j]=-INF;        }        for(int i=1;i<=m;i++)        {           int a,b,c;           scanf("%d%d%d",&a,&b,&c);           if(-c>w[a][b])           w[a][b]=-c;        }        printf("%d\n",-KM());    }    return 0;}

第五题:hdu  3435  A new Graph Game

http://acm.hdu.edu.cn/showproblem.php?pid=3435 
跟第三题代码基本上一样,只是要双向建图,也有重边

福大 AekdyCoin 出的题,好险的题啊,时间跑了 2000+ ;

/*其实在求最大 最小的时候只要用一个模板就行了,把边的权值去相反数即可得到另外一个.求结果的时候再去相反数即可*//*最大最小有一些地方不同。。*/#include <iostream>#include<cstring>#include<cstdio>#include<cmath>int n,m;const int maxn = 1005;const int INF = 0xffffff;int w[maxn][maxn];int lx[maxn],ly[maxn]; //顶标int linky[maxn];int visx[maxn],visy[maxn];int slack[maxn];bool find(int x){    visx[x] = true;    for(int y = 1; y <=n; y++)    {        if(visy[y])            continue;        int t = lx[x] + ly[y] - w[x][y];        if(t==0)        {            visy[y] = true;            if(linky[y]==-1 || find(linky[y]))            {                linky[y] = x;                return true;        //找到增广轨            }        }        else if(slack[y] > t)            slack[y] = t;    }    return false;                   //没有找到增广轨(说明顶点x没有对应的匹配,与完备匹配(相等子图的完备匹配)不符)}int KM()                //返回最优匹配的值{    int i,j;    memset(linky,-1,sizeof(linky));    memset(ly,0,sizeof(ly));    for(i = 1; i <=n; i++)    {         lx[i] = -INF;         for(j = 1; j <=n; j++)            if(w[i][j] > lx[i])                lx[i] = w[i][j];    }    for(int x = 1; x <=n; x++)    {        for(i = 1; i <=n; i++)        slack[i] = INF;        while(true)        {            memset(visx,0,sizeof(visx));            memset(visy,0,sizeof(visy));            if(find(x))                     //找到增广轨,退出            break;            int d = INF;            for(i = 1; i <=n; i++)          //没找到,对l做调整(这会增加相等子图的边),重新找            {                if(!visy[i] && d > slack[i])                    d = slack[i];            }            for(i = 1; i <=n; i++)            {                if(visx[i])                    lx[i] -= d;            }            for(i = 1; i <=n; i++)            {                if(visy[i])                     ly[i] += d;                else                    slack[i] -= d;            }        }    }}int main(){   // freopen("g:/1.txt","r",stdin);    int cas,p=1; scanf("%d",&cas);    while(p<=cas)    {        scanf("%d%d",&n,&m);        for(int i=1;i<=n;i++)        {            for(int j=1;j<=n;j++)            w[i][j]=-INF;        }        for(int i=1;i<=m;i++)        {           int a,b,c;           scanf("%d%d%d",&a,&b,&c);           if(-c>w[a][b])           w[a][b]=w[b][a]=-c;        }        KM();      int result = 0 ,flag=0;      for(int i = 1; i <=n; i++)      {        if(linky[i]==-1||w[linky[i]][i]==-INF)             {                flag=1;break;             }        else    result += w[linky[i]][i];       }        if(flag) printf("Case %d: NO\n",p++);        else printf("Case %d: %d\n",p++,-result);    }    return 0;}

第六题: hdu 2426  Interesting Housing Problem

http://acm.hdu.edu.cn/showproblem.php?pid=2426

注意 题目输入 |Vi| <= 10000  
左集是学生,右集是房子,w[i][j] < 0 不可匹配,最后无法完美匹配输出-1

/*其实在求最大 最小的时候只要用一个模板就行了,把边的权值去相反数即可得到另外一个.求结果的时候再去相反数即可*//*最大最小有一些地方不同。。*/#include <iostream>#include<cstring>#include<cstdio>#include<cmath>int n,m,v;const int maxn = 1005;const int INF = 0xffffff;int w[maxn][maxn];int lx[maxn],ly[maxn]; //顶标int linky[maxn];int visx[maxn],visy[maxn];int slack[maxn];bool find(int x){    visx[x] = true;    for(int y = 0; y < m; y++)    {        if(visy[y])            continue;        int t = lx[x] + ly[y] - w[x][y];        if(t==0)        {            visy[y] = true;            if(linky[y]==-1 || find(linky[y]))            {                linky[y] = x;                return true;        //找到增广轨            }        }        else if(slack[y] > t)            slack[y] = t;    }    return false;                   //没有找到增广轨(说明顶点x没有对应的匹配,与完备匹配(相等子图的完备匹配)不符)}int KM()                //返回最优匹配的值{    int i,j;    memset(linky,-1,sizeof(linky));    memset(ly,0,sizeof(ly));    for(i = 0; i <n; i++)    {         lx[i] = -INF;         for(j = 0; j <m; j++)            if(w[i][j] > lx[i])                lx[i] = w[i][j];    }    for(int x = 0; x <n; x++)    {        for(i = 0; i < m; i++)        slack[i] = INF;        while(true)        {            memset(visx,0,sizeof(visx));            memset(visy,0,sizeof(visy));            if(find(x))                     //找到增广轨,退出            break;            int d = INF;            for(i = 0; i < m; i++)          //没找到,对l做调整(这会增加相等子图的边),重新找            {                if(!visy[i] && d > slack[i])                    d = slack[i];            }            for(i = 0; i < n; i++)            {                if(visx[i])                    lx[i] -= d;            }            for(i = 0 ; i < m; i++)            {                if(visy[i])                     ly[i] += d;                else                    slack[i] -= d;            }        }    }}int main(){   // freopen("g:/1.txt","r",stdin);       int p=1;       while(scanf("%d%d%d",&n,&m,&v)==3)       {         for(int i=0;i<n;i++)         {            for(int j=0;j<m;j++)            w[i][j]=-INF;          }        for(int i=0;i<v;i++)        {           int a,b,c;           scanf("%d%d%d",&a,&b,&c);           if(c < 0) continue ;           w[a][b]=c;        }        if(v<=0) { printf("Case %d: -1\n",p++);continue;}        KM();        int result = 0 ,flag=0;        for(int i = 0; i < m; i++)         {         if(linky[i]>-1&&w[linky[i]][i]!=INF)         { result += w[linky[i]][i]; flag++;}         }         if(flag<n)  result=-1;         printf("Case %d: %d\n",p++,result);      }    return 0;}

第七题:hdu 2853  Assignment

http://acm.hdu.edu.cn/showproblem.php?pid=2853 
思路:让原有匹配更有优势就可以了 
实现:所有权值扩大100倍,原有匹配【例如a匹配b】w[a][b]+ + 
设结果是res 
最大值:res/100 
至少改变个数:n - res%100  

这种处理比较有意思

/*其实在求最大 最小的时候只要用一个模板就行了,把边的权值去相反数即可得到另外一个.求结果的时候再去相反数即可*//*最大最小有一些地方不同。。*/#include <iostream>#include<cstring>#include<cstdio>#include<cmath>int n,m,v;const int maxn = 1005;const int INF = 0xffffff;int w[maxn][maxn];int lx[maxn],ly[maxn]; //顶标int linky[maxn];int visx[maxn],visy[maxn];int slack[maxn];bool find(int x){    visx[x] = true;    for(int y = 1; y <= m; y++)    {        if(visy[y])            continue;        int t = lx[x] + ly[y] - w[x][y];        if(t==0)        {            visy[y] = true;            if(linky[y]==-1 || find(linky[y]))            {                linky[y] = x;                return true;        //找到增广轨            }        }        else if(slack[y] > t)            slack[y] = t;    }    return false;                   //没有找到增广轨(说明顶点x没有对应的匹配,与完备匹配(相等子图的完备匹配)不符)}void KM()                //返回最优匹配的值{    int i,j;    memset(linky,-1,sizeof(linky));    memset(ly,0,sizeof(ly));    for(i = 1; i <= n; i++)    {         lx[i] = -INF;         for(j = 1; j <=m; j++)            if(w[i][j] > lx[i])                lx[i] = w[i][j];    }    for(int x = 1; x <=n; x++)    {        for(i = 1; i <= m; i++)        slack[i] = INF;        while(true)        {            memset(visx,0,sizeof(visx));            memset(visy,0,sizeof(visy));            if(find(x))                     //找到增广轨,退出            break;            int d = INF;            for(i = 1; i <= m; i++)          //没找到,对l做调整(这会增加相等子图的边),重新找            {                if(!visy[i] && d > slack[i])                    d = slack[i];            }            for(i = 1; i <= n; i++)            {                if(visx[i])                    lx[i] -= d;            }            for(i = 1 ; i <= m; i++)            {                if(visy[i])                     ly[i] += d;                else                    slack[i] -= d;            }        }    }}int main(){   // freopen("g:/1.txt","r",stdin);       while(scanf("%d%d",&n,&m)==2)       {          // memset(w,0,sizeof(w));         for(int i=1;i<=n;i++)            for(int j=1;j<=m;j++)            {                int  c;               scanf("%d",&c);               w[i][j]=c*100;            }        int  num=0;        for(int i=1;i<=n;i++)        {            int ss;            scanf("%d",&ss);            num+=w[i][ss];            w[i][ss]++;        }        KM();        int result = 0  ;        for(int i = 1; i <= m; i++)         {          if(linky[i]!=-1)              result += w[linky[i]][i];         }         printf("%d %d\n",n-result%100,result/100-num/100);      }    return 0;}

第八题:hdu 3718  

http://acm.hdu.edu.cn/showproblem.php?pid=3718 
题目求的是两字符串的最大相似度 
思路:因为第一个串的一种字母只能匹配第二个串的一种字母,所以可以转化为求 
【字母的最大匹配值/n】 注意输入 scanf--%s   不要用 %c%*c 

/*其实在求最大 最小的时候只要用一个模板就行了,把边的权值去相反数即可得到另外一个.求结果的时候再去相反数即可*//*最大最小有一些地方不同。。*/#include <iostream>#include<cstring>#include<cstdio>#include<cmath>int n,m,v;const int maxn = 30;const int INF = 0xffffff;int w[maxn][maxn];int lx[maxn],ly[maxn]; //顶标int linky[maxn];int visx[maxn],visy[maxn];int slack[maxn];bool find(int x){    visx[x] = true;    for(int y = 1; y <27; y++)    {        if(visy[y])            continue;        int t = lx[x] + ly[y] - w[x][y];        if(t==0)        {            visy[y] = true;            if(linky[y]==-1 || find(linky[y]))            {                linky[y] = x;                return true;        //找到增广轨            }        }        else if(slack[y] > t)            slack[y] = t;    }    return false;                   //没有找到增广轨(说明顶点x没有对应的匹配,与完备匹配(相等子图的完备匹配)不符)}void KM()                //返回最优匹配的值{    int i,j;    memset(linky,-1,sizeof(linky));    memset(ly,0,sizeof(ly));    for(i = 1; i < 27; i++)    {         lx[i] = -INF;         for(j = 1; j <27; j++)            if(w[i][j] > lx[i])                lx[i] = w[i][j];    }    for(int x = 1; x <27; x++)    {        for(i = 1; i < 27; i++)        slack[i] = INF;        while(true)        {            memset(visx,0,sizeof(visx));            memset(visy,0,sizeof(visy));            if(find(x))                     //找到增广轨,退出            break;            int d = INF;            for(i = 1; i < 27; i++)          //没找到,对l做调整(这会增加相等子图的边),重新找            {                if(!visy[i] && d > slack[i])                    d = slack[i];            }            for(i = 1; i < 27; i++)            {                if(visx[i])                    lx[i] -= d;            }            for(i = 1 ; i <27; i++)            {                if(visy[i])                     ly[i] += d;                else                    slack[i] -= d;            }        }    }}int main(){   // freopen("g:/1.txt","r",stdin);       char str[10005];       int t;scanf("%d",&t);       while(t--)       {           int v;           scanf("%d%d%d%*c",&n,&m,&v);           for(int i=0;i < n;i++)            {               char   c[5];               scanf("%s",c);               str[i]=c[0];            }         for(int i=0;i< v;i++)         {            memset(w,0,sizeof(w));            char ss[5];            for(int j=0;j < n;j++)            {             scanf("%s",&ss);             w[str[j]-'A'+1][ss[0]-'A'+1]++;            }           /* for(int kk=1;kk<=27;kk++)            {                for(int jj=1;jj<=27;jj++)                printf("%d",w[kk][jj]);                putchar('\n');            }*/            KM();            double  result = 0  ;            for(int i = 1; i < 27; i++)           {               if(linky[i]!=-1)              result += w[linky[i]][i];           }         printf("%.4f\n",result/n);        }       }    return 0;}

推荐题目链接:

http://972169909-qq-com.iteye.com/blog/1184514




0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 注册公司居委会不盖章怎么办 营业执照名字和店名不一样怎么办 开炸鸡店没经验怎么办 提名候选人时重名重姓怎么办 别人用我的店名怎么办 wish店铺出现侵权产品怎么办 如果公司缺人该怎么办 鲁班奖证书丢了怎么办 个人注册服务号没有营业执照怎么办 社保过了缴费日怎么办 被评为d级纳税人怎么办 忘了税号tfn怎么办 个体户没有办税务登记怎么办 遇征地企业不搬怎么办 dnf账号改错名了怎么办 银行卡绑定的手机号码换了怎么办 支付宝手机号码换了怎么办 淘宝账号被注销了怎么办 注销淘宝号绑定的手机号怎么办 淘宝账号不小心注销了怎么办 淘宝旧密码忘了怎么办 淘宝登录原始密码忘记了怎么办 微信原始密码忘记了怎么办 优酷会员重复交费怎么办 微信解绑手机号密码忘了怎么办 闪银呼呼逾期5天怎么办 忘记淘宝账号和密码怎么办 蘑菇街账号忘了怎么办 台式电脑密码忘记了怎么办 单位社保登陆密码忘记了怎么办 12306的登录密码忘了怎么办 网银支付密码忘了怎么办 邮政网银密码忘了怎么办 12306新注册待核验怎么办 建行网银盾密码忘了怎么办 建行网银登陆密码忘了怎么办 建行网银密码忘了怎么办 建行手机网银密码忘了怎么办 移动宽带账号密码忘了怎么办 移动宽带忘记账号密码怎么办 宽带账号密码忘了怎么办