二分图变种之最小路径覆盖、最小点覆盖集【poj3041】【poj2060】

这里写图片描述
[pixiv] https://www.pixiv.net/member_illust.php?mode=medium&illust_id=54859604
向大(hei)佬(e)势力学(di)习(tou)

二分图其实早就学了,可是无赖自己当初没好好听讲,变种就不说了,连匈牙利算法都不会。这次给了我一个好好复习改过自新的机会,既把匈牙利搞熟了,也算是理解了一些变种。

最小路径覆盖:

题意
用最少的出租车送完所有的乘客

将时间上可以接上的乘客连边
在一个图中,希望用最少的路径将所有点走遍,为什么 最小路径数=点总数-最大匹配 呢?
一张图,如果我们先暴力的各用一条路径把点覆盖
(拆点,连边表示可到达,虚线是假设的)
这里写图片描述
那么每多一个匹配,就可以少一个路径(车)。
代码

#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;

const int oo=0x3f3f3f3f;

int m,h[505],mm[505],tim[505],st[505][2],en[505][2];
int top,hh,head[1010];
struct Node{
    int to,nxt;
}a[250010];
int vis[1010],girl[1010];

void adde(int fr,int to,int val){
    hh++;
    a[hh].to=to;
    a[hh].nxt=head[fr];
}
int dis(int x){
    return abs(st[x][0]-en[x][0])+abs(st[x][1]-en[x][1]);
}
int diss(int a,int b){
    return abs(en[a][0]-st[b][0])+abs(en[a][1]-st[b][1]);
}
bool find(int x){//printf("/");
    for(int i=head[x];i;i=a[i].nxt){//printf("he ");
        int v=a[i].to;
        if(!vis[v]){
            vis[v]=1;
            if(girl[v]==0||find(girl[v])){
                girl[v]=x;
                return true;
            }
        }
    }
    return false;
}
int hungary(){
    int rt=0;
    for(int i=1;i<=m;i++){
        memset(vis,0,sizeof(vis));
        if(find(i)) rt++;
    }
    return rt;
}
void solve(){
    memset(a,0,sizeof(a));
    memset(head,0,sizeof(head));
    memset(girl,0,sizeof(girl));
    scanf("%d",&m);
    top=2*m+1;hh=1;
    char str[10];
    for(int i=1;i<=m;i++){
        scanf("%s",str);
        h[i]=(str[0]-'0')*10+(str[1]-'0');
        mm[i]=(str[3]-'0')*10+(str[4]-'0');
        tim[i]=h[i]*60+mm[i];//printf("tim[%d]=%d\n",i,tim[i]);
        scanf("%d%d%d%d",&st[i][0],&st[i][1],&en[i][0],&en[i][1]);
    }
    for(int i=1;i<m;i++){
        for(int j=i+1;j<=m;j++){
            if(tim[i]+dis(i)+diss(i,j)<tim[j]){
                adde(i,m+j,1);//printf("he ");
            }
        }
    }
    int ans=0;
    ans=hungary();
    printf("%d\n",m-ans);
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        solve();
    }
    return 0;
}

最小点覆盖集:
最小点覆盖=最大匹配
为什么呢?假设还有一条边没有被覆盖,即它连的两个点都还没有被纳入匹配,那么这又是一个匹配了

题意
在n*n的方格中,放着一些东西。我有一个武器,每次可以清除一横排或一纵列,问最小使用次数

裸体一道,A的人太多了。然而对初学者来说,是开拓新思维。将行与列拆成两个点,两边即为一个点
代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=10000+5;

int n,k,r[N],c[N];
int head[3*N],end[6*N],nxt[6*N],hh=0;
int vis[3*N],girl[3*N];

void adde(int a,int b){
    hh++;
    end[hh]=b;
    nxt[hh]=head[a];
    head[a]=hh;
}
bool find(int u){
    for(int i=head[u];i;i=nxt[i]){
        int v=end[i];
        if(!vis[v]){
            vis[v]=1;
            if(girl[v]==0||find(girl[v])){
                girl[v]=u;
                return true;
            }
        }
    }
    return false;
}
int hungary(){
    int rt=0;
    for(int i=1;i<=n;i++){
        memset(vis,0,sizeof(vis));
        if(find(i)) rt++;
    }
    return rt;
}
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=k;i++){
        scanf("%d%d",&r[i],&c[i]);
        adde(r[i],c[i]+n);//建边注意 
    }
    printf("%d",hungary());
    return 0;
}

总结:
1、此次算是学懂了一些了,二分图的变通就在于构图上,其中拆点的思想尤为重要,还需多练

posted @ 2017-10-31 19:09  LinnBlanc  阅读(149)  评论(0编辑  收藏