KM算法 入門——[kuangbin]KM匹配

之前寫過了關於普通二分匹配的相關題目了,就是尋找儘量多的邊使得任意邊連接的兩點都沒有與其他邊相連,而km算法解決的則是在帶權的二分圖中尋找權值和最大的匹配,可以通過先給無連接的點連上權值爲0或者負無窮(求最小權值和)的邊,使得問題變成找到權值和最大的完美匹配。

簡單來說,KM算法就是先限定好了最終的權值和然後尋找能不能在這個條件下找到完美匹配,給予左右兩邊的點一個頂標l的概念,只有l[x]+l[y]==w[x][y]的情況下,這條邊此時才能進行匹配,一開始頂標的設置爲:左邊點的頂標爲與該點相連的邊的最大權值,右邊點爲0.若能找到完美匹配,則得到答案,否則我們就得修改頂標使得另一些原本不能用來匹配的邊可以進行匹配,不過權值和比之前降低了。

A - 奔小康賺大錢 HDU - 2255

裸題

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=305;
const int inf=0x3f3f3f3f;
int n;
int w[maxn][maxn];
int lx[maxn],ly[maxn];
int matched[maxn];
int slack[maxn];
bool s[maxn],t[maxn];

bool match(int i){
    s[i]=1;
    for(int j=1;j<=n;j++){
        int cnt=lx[i]+ly[j]-w[i][j];
        if(cnt==0&&!t[j]){
            t[j]=1;
            if(!matched[j]||match(matched[j])){
                matched[j]=i;
                return 1;
            }
        }
        else{
            slack[j]=min(slack[j],cnt);
        }
    }
    return 0;
}

void update(){
    int a=inf;
    for(int i=1;i<=n;i++){
        if(!t[i])a=min(a,slack[i]);
    }
    for(int i=1;i<=n;i++){
        if(s[i])lx[i]-=a;
        if(t[i])ly[i]+=a;
    }
}

void km(){
    memset(matched,0,sizeof(matched));
    memset(lx,0,sizeof(lx));
    memset(ly,0,sizeof(ly));
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            lx[i]=max(lx[i],w[i][j]);
        }
    }
    for(int i=1;i<=n;i++){
        memset(slack,0x3f,sizeof(slack));
        while(1){
            memset(s,0,sizeof(s));
            memset(t,0,sizeof(t));
            if(match(i))break;
            else update();
        }
    }
}

int main()
{
    while(~scanf("%d",&n)){
        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+=lx[i];
        ans+=ly[i];
    }
    printf("%d\n",ans);
    }
    return 0;
}

B - Going Home HDU - 1533

n個人n個家,給每個人分配一個home使得所有人回家需要的時間總和最小,通過bfs處理出二分圖的邊權取負之後km便可。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=105;
const int inf=0x3f3f3f3f;
int n,m;
int w[maxn][maxn];
int lx[maxn],ly[maxn];
int matched[maxn];
int slack[maxn];
bool s[maxn],t[maxn];

bool match(int i){
    s[i]=1;
    for(int j=1;j<=n;j++){
        int cnt=lx[i]+ly[j]-w[i][j];
        if(cnt==0&&!t[j]){
            t[j]=1;
            if(!matched[j]||match(matched[j])){
                matched[j]=i;
                return 1;
            }
        }
        else{
            slack[j]=min(slack[j],cnt);
        }
    }
    return 0;
}

void update(){
    int a=inf;
    for(int i=1;i<=n;i++){
        if(!t[i])a=min(a,slack[i]);
    }
    for(int i=1;i<=n;i++){
        if(s[i])lx[i]-=a;
        if(t[i])ly[i]+=a;
    }
}

void km(){
    memset(matched,0,sizeof(matched));
    memset(lx,0,sizeof(lx));
    memset(ly,0,sizeof(ly));
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            lx[i]=max(lx[i],w[i][j]);
        }
    }
    for(int i=1;i<=n;i++){
        memset(slack,0x3f,sizeof(slack));
        while(1){
            memset(s,0,sizeof(s));
            memset(t,0,sizeof(t));
            if(match(i))break;
            else update();
        }
    }
}
char ma[105][105];
int idd[105][105];
bool vis[105][105];
int dx[4]={1,0,0,-1};
int dy[4]={0,1,-1,0};
struct node{
    int x,y,step;
};
void bfs(int id,int x,int y){
    queue<node>que;
    que.push({x,y,0});
    vis[x][y]=1;
    while(!que.empty()){
        node cnt=que.front();que.pop();
        if(ma[cnt.x][cnt.y]=='m')w[id][idd[cnt.x][cnt.y]]=-cnt.step;
        for(int i=0;i<4;i++){
            int nx=cnt.x+dx[i],ny=cnt.y+dy[i];
            if(nx>=0&&nx<n&&ny>=0&&ny<m&&!vis[nx][ny]){
                vis[nx][ny]=1;
                que.push({nx,ny,cnt.step+1});
            }
        }
    }

}

int main()
{
   while(scanf("%d%d",&n,&m),n+m){
        for(int i=0;i<n;i++){
            scanf("%s",ma[i]);
        }
        int tot=1;
        for(int i=0;i<n;i++){
            for(int j=0;j<m;j++){
                if(ma[i][j]=='m'){
                   idd[i][j]=tot++;
                }
            }
        }
        tot=1;
        for(int i=0;i<n;i++){
            for(int j=0;j<m;j++){
                if(ma[i][j]=='H'){
                    memset(vis,0,sizeof(vis));
                    bfs(tot++,i,j);
                }
            }
        }
        n=tot-1;
        km();
        int ans=0;
        for(int i=1;i<=n;i++){
            ans+=lx[i];ans+=ly[i];
        }
        printf("%d\n",-ans);
   }
    return 0;
}

C - Interesting Housing Problem HDU - 2426

學生給不同宿舍評價,分配宿舍給學生使得總得分最高,沒有被i學生評分或評價爲負數的宿舍不能分配給該學生,求能否給所有學生分配宿舍並求總分最高值。
把不能連的邊權設爲-inf,最後根據匹配邊中有沒有-inf的邊就能簡單判斷是否有解了。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=505;
const int inf=0x2f2f2f2f;
int n,m,e;
int w[maxn][maxn];
int lx[maxn],ly[maxn];
int matched[maxn];
int slack[maxn];
bool s[maxn],t[maxn];

bool match(int i){
    s[i]=1;
    for(int j=1;j<=n;j++){
        int cnt=lx[i]+ly[j]-w[i][j];
        if(cnt==0&&!t[j]){
            t[j]=1;
            if(!matched[j]||match(matched[j])){
                matched[j]=i;
                return 1;
            }
        }
        else{
            slack[j]=min(slack[j],cnt);
        }
    }
    return 0;
}

void update(){
    int a=inf;
    for(int i=1;i<=n;i++){
        if(!t[i])a=min(a,slack[i]);
    }
    for(int i=1;i<=n;i++){
        if(s[i])lx[i]-=a;
        if(t[i])ly[i]+=a;
    }
}

void km(){
    memset(matched,0,sizeof(matched));
    for(int i=1;i<=n;i++){lx[i]=-inf;}
    memset(ly,0,sizeof(ly));
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            lx[i]=max(lx[i],w[i][j]);
        }
    }
    for(int i=1;i<=n;i++){
        memset(slack,0x3f,sizeof(slack));
        while(1){
            memset(s,0,sizeof(s));
            memset(t,0,sizeof(t));
            if(match(i))break;
            else update();
        }
    }
}


int main()
{
    int a,b,c;
    int cas=0;
    while(~scanf("%d%d%d",&m,&n,&e)){
        cas++;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                w[i][j]=-inf;
            }
        }
        for(int i=0;i<e;i++){
            scanf("%d%d%d",&a,&b,&c);
            a++;b++;
            if(c>=0)
            w[b][a]=c;
        }
        if(m>n){printf("Case %d: -1\n",cas);continue;}
        km();
        int ans=0;
        for(int i=1;i<=m;i++){
            int cnt=w[matched[i]][i];
            if(cnt!=-inf){
                ans+=cnt;
            }
            else{ans=-1;break;}
        }
        printf("Case %d: %d\n",cas,ans);
    }
    return 0;
}

D - Special Fish HDU - 3395
比較裸的km

#include <cstdio>
#include<string.h>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=105;
const int inf=0x3f3f3f3f;
int n,m,e;
int w[maxn][maxn];
int lx[maxn],ly[maxn];
int matched[maxn];
int slack[maxn];
bool s[maxn],t[maxn];

bool match(int i){
    s[i]=1;
    for(int j=1;j<=n;j++){
        int cnt=lx[i]+ly[j]-w[i][j];
        if(cnt==0&&!t[j]){
            t[j]=1;
            if(!matched[j]||match(matched[j])){
                matched[j]=i;
                return 1;
            }
        }
        else{
            slack[j]=min(slack[j],cnt);
        }
    }
    return 0;
}

void update(){
    int a=inf;
    for(int i=1;i<=n;i++){
        if(!t[i])a=min(a,slack[i]);
    }
    for(int i=1;i<=n;i++){
        if(s[i])lx[i]-=a;
        if(t[i])ly[i]+=a;
    }
}

void km(){
    memset(matched,0,sizeof(matched));
    memset(lx,0,sizeof(lx));
    memset(ly,0,sizeof(ly));
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            lx[i]=max(lx[i],w[i][j]);
        }
    }
    for(int i=1;i<=n;i++){
        memset(slack,0x3f,sizeof(slack));
        while(1){
            memset(s,0,sizeof(s));
            memset(t,0,sizeof(t));
            if(match(i))break;
            else update();
        }
    }
}

int val[105];
int main()
{
    char str[105];
    while(~scanf("%d",&n),n){
        memset(w,0,sizeof(w));
        for(int i=1;i<=n;i++){scanf("%d",&val[i]);}
        for(int i=0;i<n;i++){
            scanf("%s",str);
            for(int j=0;j<n;j++){
                if(str[j]=='1')w[i+1][j+1]=val[i+1]^val[j+1];
            }
        }
        km();
        int ans=0;
        for(int i=1;i<=n;i++){
            ans+=lx[i];ans+=ly[i];
        }
        printf("%d\n",ans);
    }
    return 0;
}

E - Chocolate HDU - 2282

巧克力可以移動到相鄰的盒子裏,求使所有盒子裏都只有不多於一個巧克力所需要的最小移動次數。
處理巧克力與所有盒子的距離建邊取負即可,求距離需要注意。。

#include <cstdio>
#include<string.h>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=505;
const int inf=0x2f2f2f2f;
int n,m,e;
int w[maxn][maxn];
int lx[maxn],ly[maxn];
int matched[maxn];
int slack[maxn];
bool s[maxn],t[maxn];
//w cal wa,lx's init wa
bool match(int i){
    s[i]=1;
    for(int j=1;j<=n;j++){
        int cnt=lx[i]+ly[j]-w[i][j];
        if(cnt==0&&!t[j]){
            t[j]=1;
            if(!matched[j]||match(matched[j])){
                matched[j]=i;
                return 1;
            }
        }
        else{
            slack[j]=min(slack[j],cnt);
        }
    }
    return 0;
}

void update(){
    int a=inf;
    for(int i=1;i<=n;i++){
        if(!t[i])a=min(a,slack[i]);
    }
    for(int i=1;i<=n;i++){
        if(s[i])lx[i]-=a;
        if(t[i])ly[i]+=a;
    }
}

void km(){
    memset(matched,0,sizeof(matched));
   // memset(lx,0,sizeof(lx));
    for(int i=1;i<=n;i++)lx[i]=-inf;
    memset(ly,0,sizeof(ly));
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            lx[i]=max(lx[i],w[i][j]);
        }
    }
    for(int i=1;i<=n;i++){
        memset(slack,0x3f,sizeof(slack));
        while(1){
            memset(s,0,sizeof(s));
            memset(t,0,sizeof(t));
            if(match(i))break;
            else update();
        }
    }
}

int num[505];
int main()
{
    while(~scanf("%d",&n)){
    int tot=1;
    memset(w,0,sizeof(w));
    for(int i=1;i<=n;i++){
        scanf("%d",&num[i]);
    }
    for(int i=1;i<=n;i++){
        int cnt=num[i];
        for(int j=1;j<=cnt;j++){
            for(int k=1;k<=n;k++){
                w[tot][k]=-min(abs(k-i),n-abs(i-k));
            }
            tot++;
        }
    }
    km();
    int ans=0;
    for(int i=1;i<=n;i++){
        ans+=lx[i],ans+=ly[i];
    }
    printf("%d\n",-ans);
    }
    return 0;
}

F - One fihgt one HDU - 2813

裸題,處理名字轉化爲編號進行建邊就行,我用的map處理

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=205;
const int inf=0x3f3f3f3f;
int n,m,k;
int w[maxn][maxn];
int lx[maxn],ly[maxn];
int matched[maxn];
int slack[maxn];
bool s[maxn],t[maxn];
//slack的更新錯誤
bool match(int i){
    s[i]=1;
    for(int j=1;j<=m;j++){
        int cnt=lx[i]+ly[j]-w[i][j];
        if(cnt==0&&!t[j]){
            t[j]=1;
            if(!matched[j]||match(matched[j])){
                matched[j]=i;
                return 1;
            }
        }
        else{
            slack[j]=min(slack[j],cnt);
        }
    }
    return 0;
}

void update(){
    int a=inf;
    for(int i=1;i<=m;i++){
        if(!t[i])a=min(a,slack[i]);
    }
    for(int i=1;i<=n;i++){
        if(s[i])lx[i]-=a;
    }
    for(int i=1;i<=m;i++){
        if(t[i])ly[i]+=a;
    }
}

void km(){
    memset(matched,0,sizeof(matched));
    memset(lx,-inf,sizeof(lx));
    memset(ly,0,sizeof(ly));
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            lx[i]=max(lx[i],w[i][j]);
        }
    }
    for(int i=1;i<=n;i++){
        memset(slack,0x3f,sizeof(slack));
        while(1){
            memset(s,0,sizeof(s));
            memset(t,0,sizeof(t));
            if(match(i))break;
            else update();
        }
    }
}

int main()
{
    while(~scanf("%d%d%d",&n,&m,&k)){
        memset(w,-inf,sizeof(w));
        char na[25],nb[25];
        int tota=1,totb=1,c;
        map<string,int> ma;
        for(int i=0;i<k;i++){
            scanf("%s%s%d",na,nb,&c);
            if(ma.find(na)==ma.end())ma[na]=tota++;
            if(ma.find(nb)==ma.end())ma[nb]=totb++;
            int cnta=ma[na],cntb=ma[nb];
            w[cnta][cntb]=-c;
        }
        km();
        int ans=0;
        for(int i=1;i<=m;i++){
            if(matched[i]&&matched[i]<=n){
                ans+=w[matched[i]][i];
            }
        }
        printf("%d\n",-ans);
    }
}

G - Cyclic Tour HDU - 1853

尋找環使得每個點都被訪問過且路徑權值和最小
只要每個點的入度和出度都爲1就可以成環了,注意重邊。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=105;
const int inf=0x3f3f3f3f;
int n,m,k;
int w[maxn][maxn];
int lx[maxn],ly[maxn];
int matched[maxn];
int slack[maxn];
bool s[maxn],t[maxn];
int head[maxn];
//cheng huan tiao jian cuowu && chongbian
bool match(int i){
    s[i]=1;
    for(int j=1;j<=m;j++){
        int cnt=lx[i]+ly[j]-w[i][j];
        if(cnt==0&&!t[j]){
            t[j]=1;
            if(!matched[j]||match(matched[j])){
                matched[j]=i;
                head[j]=head[i];
                return 1;
            }
        }
        else{
            slack[j]=min(slack[j],cnt);
        }
    }
    return 0;
}

void update(){
    int a=inf;
    for(int i=1;i<=m;i++){
        if(!t[i])a=min(a,slack[i]);
    }
    for(int i=1;i<=n;i++){
        if(s[i])lx[i]-=a;
    }
    for(int i=1;i<=m;i++){
        if(t[i])ly[i]+=a;
    }
}

void km(){
    memset(matched,0,sizeof(matched));
    memset(lx,-0x3f,sizeof(lx));
    memset(ly,0,sizeof(ly));
    for(int i=1;i<=n;i++){
        head[i]=i;
        for(int j=1;j<=m;j++){
            lx[i]=max(lx[i],w[i][j]);
        }
    }
    for(int i=1;i<=n;i++){
        memset(slack,0x3f,sizeof(slack));
        while(1){
            memset(s,0,sizeof(s));
            memset(t,0,sizeof(t));
            if(match(i))break;
            else update();
        }
    }
}

int main()
{
    int a,b,c;
    while(~scanf("%d%d",&n,&k)){
        m=n;
        memset(w,-0x3f,sizeof(w));
        for(int i=0;i<k;i++){
            scanf("%d%d%d",&a,&b,&c);
            if(-c>w[a][b])
            w[a][b]=-c;
        }
        km();
        int ans=0;
        for(int i=1;i<=n;i++){
            int cnt=w[matched[i]][i];
            if(cnt==w[0][0]){ans=1;break;}
            ans+=cnt;
        }
        printf("%d\n",-ans);
    }
}

H - Tour HDU - 3488

跟上題一樣

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=205;
const int inf=0x3f3f3f3f;
int n,m,k;
int w[maxn][maxn];
int lx[maxn],ly[maxn];
int matched[maxn];
int slack[maxn];
bool s[maxn],t[maxn];
int head[maxn];

bool match(int i){
    s[i]=1;
    for(int j=1;j<=m;j++){
        int cnt=lx[i]+ly[j]-w[i][j];
        if(cnt==0&&!t[j]){
            t[j]=1;
            if(!matched[j]||match(matched[j])){
                matched[j]=i;
                head[j]=head[i];
                return 1;
            }
        }
        else{
            slack[j]=min(slack[j],cnt);
        }
    }
    return 0;
}

void update(){
    int a=inf;
    for(int i=1;i<=m;i++){
        if(!t[i])a=min(a,slack[i]);
    }
    for(int i=1;i<=n;i++){
        if(s[i])lx[i]-=a;
    }
    for(int i=1;i<=m;i++){
        if(t[i])ly[i]+=a;
    }
}

void km(){
    memset(matched,0,sizeof(matched));
    memset(lx,-0x3f,sizeof(lx));
    memset(ly,0,sizeof(ly));
    for(int i=1;i<=n;i++){
        head[i]=i;
        for(int j=1;j<=m;j++){
            lx[i]=max(lx[i],w[i][j]);
        }
    }
    for(int i=1;i<=n;i++){
        memset(slack,0x3f,sizeof(slack));
        while(1){
            memset(s,0,sizeof(s));
            memset(t,0,sizeof(t));
            if(match(i))break;
            else update();
        }
    }
}

int main()
{
    int a,b,c;
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&k);
        m=n;
        memset(w,-0x3f,sizeof(w));
        for(int i=0;i<k;i++){
            scanf("%d%d%d",&a,&b,&c);
            if(-c>w[a][b])
            w[a][b]=-c;
        }
        km();
        int ans=0;
        for(int i=1;i<=n;i++){
            int cnt=w[matched[i]][i];
            if(cnt==w[0][0]){ans=1;break;}
            ans+=cnt;
        }
        printf("%d\n",-ans);
    }
}

I - A new Graph Game HDU - 3435

找哈密頓圖,使用的代碼跟上面兩題一樣,但感覺哈密頓圖不應該只有一個環嗎?

J - Card Game HDU - 3722

也是比較裸的題,按題意處理邊權即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1006;
const int inf=0x3f3f3f3f;
int n,m,k;
int w[maxn][maxn];
int lx[maxn],ly[maxn];
int matched[maxn];
int slack[maxn];
bool s[maxn],t[maxn];
//selfloop always equals to 0 && lower mistake
bool match(int i){
    s[i]=1;
    for(int j=1;j<=m;j++){
        int cnt=lx[i]+ly[j]-w[i][j];
        if(cnt==0&&!t[j]){
            t[j]=1;
            if(!matched[j]||match(matched[j])){
                matched[j]=i;
                return 1;
                }
        }
        else{
            slack[j]=min(slack[j],cnt);
        }
    }
    return 0;
}

void update(){
    int a=inf;
    for(int i=1;i<=m;i++){
        if(!t[i])a=min(a,slack[i]);
    }
    for(int i=1;i<=n;i++){
        if(s[i])lx[i]-=a;
    }
    for(int i=1;i<=m;i++){
        if(t[i])ly[i]+=a;
    }
}

void km(){
    memset(matched,0,sizeof(matched));
    memset(lx,0,sizeof(lx));
    memset(ly,0,sizeof(ly));
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            lx[i]=max(lx[i],w[i][j]);
        }
    }
    for(int i=1;i<=n;i++){
        memset(slack,0x3f,sizeof(slack));
        while(1){
            memset(s,0,sizeof(s));
            memset(t,0,sizeof(t));
            if(match(i))break;
            else update();
        }
    }
}
char str[205][1001];
int cal(char *a,char *b){
    int lena=strlen(a);
    int lenb=strlen(b);
    int i=0;
    for(;i<lena&&i<lenb;i++){
        if(b[i]!=a[lena-1-i])break;
    }
    return i;
}

int main()
{
    while(~scanf("%d",&n)){
        m=n;
        memset(w,0,sizeof(w));
        for(int i=1;i<=n;i++)scanf("%s",str[i]);
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(i==j)w[i][j]=0;
                else w[i][j]=cal(str[i],str[j]);
            }
        }
        km();
        int ans=0;
        for(int i=1;i<=n;i++){
            ans+=lx[i],ans+=ly[i];
        }
        printf("%d\n",ans);
    }
}

K - Similarity HDU - 3718

對點分類,每個人分類用的編號不同,求兩組答案最大的相似度爲多少,也是處理建邊就行。用map似乎超時?直接開[26][26]的數組做就行。

#include<cstdio>
#include<algorithm>
#include<string.h>
#include<map>
using namespace std;
typedef long long ll;
const int maxn = 30;
const int inf = 0x3f3f3f3f;
int n, m, k, m1;
int w[maxn][maxn];
int lx[maxn], ly[maxn];
int matched[maxn];
int slack[maxn];
bool s[maxn], t[maxn];
bool match(int i){
    s[i] = 1;
    for (int j = 1; j <= m; j++){
        int cnt = lx[i] + ly[j] - w[i][j];
        if (cnt == 0 && !t[j]){
            t[j] = 1;
            if (!matched[j] || match(matched[j])){
                matched[j] = i;
                return 1;
            }
        }
        else{
            slack[j] = min(slack[j], cnt);
        }
    }
    return 0;
}

void update(){
    int a = inf;
    for (int i = 1; i <= m; i++){
        if (!t[i])a = min(a, slack[i]);
    }
    for (int i = 1; i <= n; i++){
        if (s[i])lx[i] -= a;
    }
    for (int i = 1; i <= m; i++){
        if (t[i])ly[i] += a;
    }
}

void km(){
    memset(matched, 0, sizeof(matched));
    memset(lx, 0, sizeof(lx));
    memset(ly, 0, sizeof(ly));
    for (int i = 1; i <= n; i++){
        for (int j = 1; j <= m; j++){
            lx[i] = max(lx[i], w[i][j]);
        }
    }
    for (int i = 1; i <= n; i++){
        memset(slack, 0x3f, sizeof(slack));
        while (1){
            memset(s, 0, sizeof(s));
            memset(t, 0, sizeof(t));
            if (match(i))break;
            else update();
        }
    }
}

int s1[10005];
int main()
{
    int t;
    scanf("%d", &t);
    while (t--){
        scanf("%d%d%d", &n, &k, &m1);
        int aass=n;
        double n1 = n;
        int tot1 = 0, tot2;
        char c[2];
        for (int i = 0; i<n; i++){
            scanf("%s", c);
            char cnt = c[0];
            s1[i] =cnt-'A'+1;
        }
        for (int i = 0; i<m1; i++){
            memset(w, 0, sizeof(w));
            tot2 = 0;
            for (int j = 0; j<aass; j++){
                scanf("%s", c);
                char cnt = c[0];
                w[s1[j]][cnt - 'A'+1]++;
            }
            n = m = 26;
            km();
            double ans = 0;
            for (int j = 1; j <= m; j++){
                if (matched[j]){
                    ans += w[matched[j]][j];
                }
            }
            ans = ans / n1;
            printf("%.4lf\n", ans);
        }
    }
    return 0;
}

L - Mining Station on the Sea HDU - 2448

船返回港口的問題,用floyd處理出所有點對間的最短路來建邊,需要注意的是船入港後不能再駛出,所以從港口連出的邊邊權得爲inf,否則會有船的最短路是經過港口的。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 105;
const int inf = 0x3f3f3f3f;
int n, m, m1, k, p;
int w[maxn][maxn];
int lx[maxn], ly[maxn];
int matched[maxn];
int slack[maxn];
bool s[maxn], t[maxn];
//入港口後就不能出港口了,dis的更新錯誤
bool match(int i){
    s[i] = 1;
    for (int j = 1; j <= m; j++){
        int cnt = lx[i] + ly[j] - w[i][j];
        if (cnt == 0 && !t[j]){
            t[j] = 1;
            if (!matched[j] || match(matched[j])){
                matched[j] = i;
                return 1;
            }
        }
        else{
            slack[j] = min(slack[j], cnt);
        }
    }
    return 0;
}

void update(){
    int a = inf;
    for (int i = 1; i <= m; i++){
        if (!t[i])a = min(a, slack[i]);
    }
    for (int i = 1; i <= n; i++){
        if (s[i])lx[i] -= a;
    }
    for (int i = 1; i <= m; i++){
        if (t[i])ly[i] += a;
    }
}

void km(){
    memset(matched, 0, sizeof(matched));
    memset(lx, -0x3f, sizeof(lx));
    memset(ly, 0, sizeof(ly));
    for (int i = 1; i <= n; i++){
        for (int j = 1; j <= m; j++){
            lx[i] = max(lx[i], w[i][j]);
        }
    }
    for (int i = 1; i <= n; i++){
        memset(slack, 0x3f, sizeof(slack));
        while (1){
            memset(s, 0, sizeof(s));
            memset(t, 0, sizeof(t));
            if (match(i))break;
            else update();
        }
    }
}

int id[105];
int dis[305][305];
int main()
{
    int a,b,c;
    while(~scanf("%d%d%d%d",&n,&m1,&k,&p)){
        m=n;
        memset(w,-0x3f,sizeof(w));
        memset(dis,0x3f,sizeof(dis));
        for(int i=0;i<n;i++){
            scanf("%d",&id[i]);
            id[i]--;
        }
        for(int i=0;i<k;i++){
            scanf("%d%d%d",&a,&b,&c);
            a--,b--;
            dis[a][b]=min(dis[a][b],c);
            dis[b][a]=dis[a][b];
        }
        for(int i=0;i<p;i++){
            scanf("%d%d%d",&a,&b,&c);
            a--;b--;
           // dis[m1+a][b]=min(dis[m1+a][b],c);
            dis[b][m1+a]=min(dis[b][m1+a],c);
        }
        for(int k=0;k<n+m1;k++){
            for(int i=0;i<n+m1;i++){
                for(int j=0;j<n+m1;j++){
                    dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
                }
            }
        }
        for(int i=0;i<n;i++){
            int cnt=id[i];
            for(int j=0;j<n;j++){
                w[i+1][j+1]=-dis[cnt][m1+j];
            }
        }
        km();
        int ans=0;
        for(int i=1;i<=n;i++){
            ans+=lx[i],ans+=ly[i];
        }
        printf("%d\n",-ans);
    }
    return 0;
}

M - Assignment HDU - 2853

題意看起來就是很裸的最大邊權二分匹配,但是要儘量不改變原來的匹配,所以需要 對邊權方法然後使原匹配邊的邊權+1,這樣就會盡量不改變原匹配邊了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 55;
const int inf = 0x3f3f3f3f;
int n, m, m1, k, p;
int w[maxn][maxn];
int lx[maxn], ly[maxn];
int matched[maxn];
int slack[maxn];
bool s[maxn], t[maxn];
int chose[maxn];
//需要放大權值並將原匹配邊的權值+1,使得儘量匹配元匹配邊
bool match(int i){
    s[i] = 1;
    for (int j = 0; j <= m; j++){
        int cnt = lx[i] + ly[j] - w[i][j];
        if (cnt == 0 && !t[j]){
            t[j] = 1;
            if (!matched[j] || match(matched[j])){
                matched[j] = i;
                return 1;
            }
        }
        else{
            slack[j] = min(slack[j], cnt);
        }
    }
    return 0;
}

void update(){
    int a = inf;
    for (int i = 1; i <= m; i++){
        if (!t[i])a = min(a, slack[i]);
    }
    for (int i = 1; i <= n; i++){
        if (s[i])lx[i] -= a;
    }
    for (int i = 1; i <= m; i++){
        if (t[i])ly[i] += a;
    }
}

void km(){
    memset(matched, 0, sizeof(matched));
    memset(lx, 0, sizeof(lx));
    memset(ly, 0, sizeof(ly));
    for (int i = 1; i <= n; i++){
        for (int j = 1; j <= m; j++){
            lx[i] = max(lx[i], w[i][j]);
        }
    }
    for (int i = 1; i <= n; i++){
        memset(slack, 0x3f, sizeof(slack));
        while (1){
            memset(s, 0, sizeof(s));
            memset(t, 0, sizeof(t));
            if (match(i))break;
            else update();
        }
    }
}

int main()
{
    while(~scanf("%d%d",&n,&m)){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                scanf("%d",&w[i][j]);
                w[i][j]*=200;
            }
        }
        for(int i=1;i<=n;i++){
            scanf("%d",&chose[i]);
            w[i][chose[i]]++;
        }
        int old=0;
        for(int i=1;i<=n;i++){
            old+=w[i][chose[i]]/200;
        }
        km();
        int ans=0;
        int num=0;
        for(int i=1;i<=m;i++){
            if(matched[i]){
                if(chose[matched[i]]!=i)num++;
                ans+=w[matched[i]][i]/200;
            }
        }
        ans-=old;
        printf("%d %d\n",num,ans);
    }
    return 0;
}

N - My Brute HDU - 3315

跟上面那題差不多的操作。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100;
const int inf = 0x3f3f3f3f;
int n, m;
int w[maxn][maxn];
int lx[maxn], ly[maxn];
int matched[maxn];
int slack[maxn];
bool s[maxn], t[maxn];
int chose[maxn];

bool match(int i){
    s[i] = 1;
    for (int j = 0; j <= m; j++){
        int cnt = lx[i] + ly[j] - w[i][j];
        if (cnt == 0 && !t[j]){
            t[j] = 1;
            if (!matched[j] || match(matched[j])){
                matched[j] = i;
                return 1;
            }
        }
        else{
            slack[j] = min(slack[j], cnt);
        }
    }
    return 0;
}

void update(){
    int a = inf;
    for (int i = 1; i <= m; i++){
        if (!t[i])a = min(a, slack[i]);
    }
    for (int i = 1; i <= n; i++){
        if (s[i])lx[i] -= a;
    }
    for (int i = 1; i <= m; i++){
        if (t[i])ly[i] += a;
    }
}

void km(){
    memset(matched, 0, sizeof(matched));
    memset(lx, -0x3f, sizeof(lx));
    memset(ly, 0, sizeof(ly));
    for (int i = 1; i <= n; i++){
        for (int j = 1; j <= m; j++){
            lx[i] = max(lx[i], w[i][j]);
        }
    }
    for (int i = 1; i <= n; i++){
        memset(slack, 0x3f, sizeof(slack));
        while (1){
            memset(s, 0, sizeof(s));
            memset(t, 0, sizeof(t));
            if (match(i))break;
            else update();
        }
    }
}

int v[maxn],h[maxn],p[maxn],a[maxn],b[maxn];

bool win(int i,int j){
    int cnta=h[i],cntb=p[j];
    int aa=a[i],bb=b[j];
    while(cnta>0&&cntb>0){
        cntb-=aa;
        if(cntb<=0)break;
        cnta-=bb;
    }
    if(cnta<=0)return 0;
    else return 1;
}

int main()
{
    while(~scanf("%d",&n),n){
        m=n;
        for(int i=1;i<=n;i++){
            scanf("%d",&v[i]);
            v[i]*=100;
        }
        for(int i=1;i<=n;i++)scanf("%d",&h[i]);
        for(int i=1;i<=n;i++)scanf("%d",&p[i]);
        for(int i=1;i<=n;i++)scanf("%d",&a[i]);
        for(int i=1;i<=n;i++)scanf("%d",&b[i]);
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                int cntx=h[i]/b[j];
                if(h[i]%b[j])cntx++;
                if(cntx*a[i]>=p[j]){w[i][j]=v[i];}
                else w[i][j]=-v[i];
                if(i==j){w[i][j]++;}
            }
        }
        km();
        int ans=0;
        int num=0;
        for(int i=1;i<=n;i++){
            int cnt=matched[i];
            if(cnt!=i)num++;
            ans+=w[cnt][i];
        }
        num=n-num;
        double gg=double(num)/n;
        gg*=100;
        if(ans>0){printf("%d %.3lf%%\n",ans/100,gg);}
        else printf("Oh, I lose my dear seaco!\n");
    }
    return 0;
}

大部分的km題需要處理的都只是邊的建立,真正匹配的算法都不需要改變,但是理解記憶下次能直接實現還是比較好的。

發佈了113 篇原創文章 · 獲贊 41 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章