很容易看出这道题是二分图最优匹配,码出来代码后,悲剧的wa了。。。后来想到了错误,却一直想不到改进的方法。我开始的思路是,求出二分图最优匹配,然后判断新的匹配和原来的匹配有多少个不一样,即为需要调整的书目。后来想到这种方法的bug,如果有a、b两个公司和c、d两个任务,对应的值都为1 2,1 2,即权值相等。这样的话,用KM算法可能改变原来的匹配,实际上是不需要改变匹配的,即调整的数目为0,然而用KM算法,可能算出的结果需调整的数目为2。
这道题的巧妙之处在于建图的巧妙,我们可以把每条权值都扩大一个倍数,如扩大K倍,K>n,n为公司的数量。这样最后算出的总权值/K即为实际的总权值。然后对题目中给出的匹配的每条边都加1,即在选择原来边的时候有优势,这样在用KM算法时,就会避免本来不需要改变的匹配被改变。即使是最优匹配为原匹配,最后求的结果为x*K+n,x为实际的总的匹配,因为K>n,所以用结果/K,取整,还是x,即为实际的总的最优匹配。求需要改变匹配的数量时,只需用n-求的结果%K即可。
题目:
3 3 2 1 3 3 2 4 1 26 2 2 1 3 2 3 1 2 3 1 2 3 1 2
2 26 1 2
KM算法 #include <iostream> #include <cstdio> #include <vector> #include <string> #include <climits> #include <string.h> #include <algorithm> using namespace std; int numcom,nummisson;//公司数量和任务数量 int map[55][55];//存储权值 int num[55];//存储公司所选任务的序号 int sx[55],sy[55];//存储顶点顶标的值 int slack[55];//存储最小值 int match[55];//判断是否被匹配的数组 int vistedx[55],vistedy[55];//判断是否访问过顶点 void init(){//初始化函数 memset(sx,0,sizeof(sx)); memset(sy,0,sizeof(sy)); memset(match,-1,sizeof(match)); memset(slack,0,sizeof(slack)); for(int i=1;i<=numcom;++i){ int x=map[i][1]; for(int j=1;j<=nummisson;++j){ if(x<map[i][j]) x=map[i][j]; }//for sx[i]=x;//左面顶标的值为边中的最大权值 }//for }//init bool dfs(int x){//找增广路的函数 vistedx[x]=true;//表明该顶点已经被访问过 for(int i=1;i<=nummisson;++i){ int xx=sx[x]+sy[i]-map[x][i]; if(!vistedy[i]&&xx==0){//顶点没被访问过,且符合最优匹配加边的条件 vistedy[i]=1; if(match[i]==-1||dfs(match[i])){ match[i]=x; return true; }//if }//if else if(slack[i]>xx)//slack[i]取最小值 slack[i]=xx; }//for return false; }//dfs void bestmatch(){//最优匹配函数 for(int i=1;i<=numcom;++i){ while(1){//一直找到匹配为止 memset(vistedx,0,sizeof(vistedx)); memset(vistedy,0,sizeof(vistedy)); for(int j=1;j<=nummisson;++j) slack[j]=INT_MAX; if(dfs(i)){//找到匹配,跳出while循环 break; }//if int dd=INT_MAX; for(int j=1;j<=nummisson;++j){ if(!vistedy[j]&&dd>slack[j])//顶点没在增广路中且取最小值 dd=slack[j]; }//for for(int j=1;j<=numcom;++j){ if(vistedx[j]) sx[j]-=dd; }//for for(int j=1;j<=nummisson;++j){ if(vistedy[j]) sy[j]+=dd; } }//while }//for }//bestmatch int main(){ //freopen("1.txt","r",stdin); while(~scanf("%d%d",&numcom,&nummisson)){ int x; for(int i=1;i<=numcom;++i){ for(int j=1;j<=nummisson;++j){ scanf("%d",&x); map[i][j]=x; map[i][j]*=100;//将权值都扩大100倍 }//for }//for int total=0;//定义总量初始值 for(int i=1;i<=numcom;++i){ scanf("%d",&num[i]); total+=map[i][num[i]]; map[i][num[i]]+=1;//原来的边有优势 }//for init();//初始化函数 bestmatch();//最优匹配 int sum=0;//初始化新的总量的值 int count=0;//初始化需要改变的公司的数量 for(int i=1;i<=nummisson;++i) { if(match[i]<1||match[i]>50)continue; sum+=map[match[i]][i]; }//for printf("%d %d\n",numcom-sum%100,sum/100-total/100); }//while return 0; }//main