[HIT暑假集训]二分图

今天这场要总结一下。

A.[NOI2009]变换序列

题目描述:
这里写图片描述
输入:
这里写图片描述
输出:
这里写图片描述

题解:
题意既然已知了所有的D(i,Ti),设其为Di,那么对于每个i,Ti的情况有两种:
①Ti=(i+Di)%n
②Ti=(i-Di+n)%n
那么从i向这两种情况连边,做一个二分图匹配就好了,如果匹配数小于n的话则不成立,输出No Answer,否则一定有解。为了使得解的字典序最小,我们建边的时候先建指向大的,再建指向小的(对于链表建边来说),然后在匈牙利算法找增广路的时候要从大的点开始找增广路。(打时间标记可以大量节约时间复杂度)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cstdlib>
#define LiangJiaJun main
#define INF 199912270
#define S 1001
#define T 1002
using namespace std;
int D[10004],n;
int tm;
int vis[10004],res[10004];
int lk[10004],h[10004],ne;
struct edge{
    int to,nt;
}e[200004];
void add(int u,int v){
     e[++ne].to=v;e[ne].nt=h[u];h[u]=ne;
}
bool dfs(int x){
     for(int i=h[x];i;i=e[i].nt){
         if(vis[e[i].to]!=tm){
            vis[e[i].to]=tm;
            if(lk[e[i].to]==-1||dfs(lk[e[i].to])){
                lk[e[i].to]=x;
                return 1;
            }
         }
     }
     return 0;
}
int w33ha(){
    ne=0;
    memset(vis,0,sizeof(vis));
    memset(h,0,sizeof(h));
    for(int i=0;i<n;i++)lk[i]=-1;
    for(int i=0;i<n;i++)scanf("%d",&D[i]);
    for(int i=0;i<n;i++){
        int a,b;
        a=(i+D[i])%n;
        b=(i-D[i]+n)%n;
        if(a<b)swap(a,b);
        add(i,a);
        add(i,b);
    }
    int ans=0;
    tm=0;
    for(int i=n-1;i>=0;i--){
        ++tm;
        if(dfs(i))++ans;
    }
    if(ans!=n)return puts("No Answer"),0;
    for(int i=0;i<n;i++)res[lk[i]]=i;
    for(int i=0;i<n-1;i++)printf("%d ",res[i]);
    printf("%d\n",res[n-1]);
    return 0;
}
int LiangJiaJun(){
    while(scanf("%d",&n)!=EOF)w33ha();
    return 0;
}

B.[POJ3020]Antenna Placement
给一个H*W(H<=40,W<=10)的矩阵,矩阵上每个点不是 ′ ∗ ′ 就是 o ′ o ′ ,求问至少用多少个2*1的矩形可以覆盖所有 ′ ∗ ′ ,矩形可以横着放也可以竖着放。

题解:
对于每一个 ′ ∗ ′ ,我们可以将其与其周围所有 ′ ∗ ′ 连边,然后答案就是最小边覆盖。即(点数-最大匹配数),由于这里每个点被算了两次,那么答案其实是( ′ ∗ ′ 个数*2 - 最大匹配数)/2

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cstdlib>
#define LiangJiaJun main
#define INF 199912270
using namespace std;
int H,W,cnt,tm;
int vis[10004];
int lk[10004],h[10004],ne;
char mp[44][14];
int dx[4]={0,1,0,-1},
    dy[4]={1,0,-1,0};
struct edge{
    int to,nt;
}e[200004];
void add(int u,int v){
     e[++ne].to=v;e[ne].nt=h[u];h[u]=ne;
}
int POI(int x,int y){
    return (x-1)*W+y;
}
bool dfs(int x){
     for(int i=h[x];i;i=e[i].nt){
         if(vis[e[i].to]!=tm){
            vis[e[i].to]=tm;
            if((!lk[e[i].to])||dfs(lk[e[i].to])){
                lk[e[i].to]=x;
                return 1;
            }
         }
     }
     return 0;
}
int w33ha(){
    ne=0;cnt=0;
    memset(vis,0,sizeof(vis));
    memset(h,0,sizeof(h));
    memset(lk,0,sizeof(lk));
    scanf("%d%d",&H,&W);
    for(int i=1;i<=H;i++){
        scanf("%s",mp[i]+1);
        for(int j=1;j<=W;j++)
            if(mp[i][j]=='*')++cnt;
    }
    for(int i=1;i<=H;i++){
        for(int j=1;j<=W;j++){
            if(mp[i][j]=='*'){
                for(int k=0;k<4;k++){
                    int nowx=i+dx[k],nowy=j+dy[k];
                    if(nowx>H||nowx<=0||nowy>W||nowy<=0)continue;
                    if(mp[nowx][nowy]=='*')add(POI(i,j),POI(nowx,nowy));
                }
            }
        }
    }
    int ans=0;
    for(int i=1;i<=H;i++){
        for(int j=1;j<=W;j++){
            if(mp[i][j]=='o')continue;
            tm++;
            if(dfs(POI(i,j)))++ans;
        }
    }
    printf("%d\n",((cnt<<1)-ans)>>1);
    return 0;
}
int LiangJiaJun(){
    int T;scanf("%d",&T);
    while(T--)w33ha();
    return 0;
}

C.[HDU1528]
二分图最大匹配
D.[POJ1274]
二分图最大匹配
E.[HDU1068]
最大点独立集,想法跟B一样
F.[HDU3729]
二分图最大匹配,注意字典序

I.[HDU2853]
KM算法,带权二分图最大匹配。为了使得尽量选择原配的边,如果(i,j)在原配中选过,则(i,j)边的权值设为w(i,j)*100+1,否则设为w(i,j)
然后求出的最大匹配x,x/100就是答案,x%100就是改了多少次。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cstdlib>
#define LiangJiaJun main
#define INF 1999122700
#define S 1001
#define T 1002
using namespace std;
int n,m,mp[54][54];
int slack[54],lk[54],lx[54],ly[54],a[54];
bool vx[54],vy[54];
bool dfs(int x){
     vx[x]=1;
     for(int i=1;i<=m;i++){
         if(vy[i])continue;
         if(mp[x][i]==lx[x]+ly[i]){
            vy[i]=1;
            if((!lk[i])||dfs(lk[i])){
                lk[i]=x;
                return 1;
            }
        }
        else{
            slack[i]=min(slack[i],lx[x]+ly[i]-mp[x][i]);
        }
    }
    return 0;
}
int cnt=0,ans=0;
int KM(){
    int temp;
    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],mp[i][j]);
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++)slack[j]=INF;
        while(1){
            memset(vx,0,sizeof(vx));
            memset(vy,0,sizeof(vy));
            if(dfs(i))break;
            else{
                temp=INF;
                for(int j=1;j<=m;j++)
                    if(!vy[j])temp=min(temp,slack[j]);
                for(int j=1;j<=n;j++){
                    if(vx[j])lx[j]-=temp;
                }
                for(int j=1;j<=m;j++){
                    if(vy[j])ly[j]+=temp;
                    else slack[j]-=temp;
                }
            }
        }
    }
    return 0;
}
int w33ha(){
    memset(mp,0,sizeof(mp));
    memset(lk,0,sizeof(lk));
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            scanf("%d",&mp[i][j]);
        }
    }
    int rek=0;
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        rek+=mp[i][a[i]];
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++)mp[i][j]*=100;
        mp[i][a[i]]++;
    }
    ans=0;
    KM();
    for(int i=1;i<=m;i++){
        ans+=mp[lk[i]][i];
    }
    printf("%d %d\n",(n-(ans%100)),(ans/100)-rek);
    return 0;
}
int LiangJiaJun(){
    while(scanf("%d%d",&n,&m)!=EOF)w33ha();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值