二分图

匈牙利算法

bzoj1854 游戏

题目大意:给定n个武器,每个武器有两个属性值,每个武器用一次,求最长连续递增序列。

思路:如果把属性值看作一排点,武器看作一排点,就是二分图最大匹配了。在匈牙利中有个visit数组,如果每次赋值的话,会tle,这里有一种很好的做法,就是把visit改成int,每次和这一次的循环变量比一下就行了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxnode 1000005
#define up 10000
using namespace std;
int point[maxnode]={0},next[maxnode*2]={0},en[maxnode*2]={0},tot=0,
    match[maxnode]={0},visit[maxnode]={0};
void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;}
bool find(int x){
    int i,j;
    for (j=point[x];j;j=next[j]){
        if (visit[en[j]]!=tot){
          visit[en[j]]=tot;
          if (!match[en[j]]||find(match[en[j]])){
            match[en[j]]=x;return true;
          }
        }
    }return false;
}
int main()
{
    int i,j,x,y,n;scanf("%d",&n);
    for (i=1;i<=n;++i){
         scanf("%d%d",&x,&y);
         add(x,i);add(y,i);
    }
    for (i=1;i<=up;++i){tot=i;if (!find(i)) break;}
    printf("%d\n",i-1);
}
View Code

 

bzoj1059 矩阵游戏

题目大意:给定一个n*n的01矩阵,判断能否通过整行或者整列交换,使得左上到右下的对角线上都是1。

思路:对于格点的二分图问题之前见到过,不过这道题还是在sunshine大爷说出算法之后才恍然大悟的。对于1的点从i到j连边,如果二分图能够完美匹配,就是Yes,否则No。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxm 40005
using namespace std;
int point[maxm],next[maxm],en[maxm],match[maxm],visit[maxm],tot;
void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;}
bool find(int u){
    int v,i,j;
    for (i=point[u];i;i=next[i])
        if (visit[j=en[i]]!=tot){
            visit[j]=tot;
            if (!match[j]||find(match[j])){
                match[j]=u;return true;
            }
        }  return false;
}
int main(){
    int n,m,t,i,j;scanf("%d",&t);
    while(t--){
        memset(point,0,sizeof(point));
        memset(next,0,sizeof(next));
        memset(visit,0,sizeof(visit));
        memset(match,0,sizeof(match));
        scanf("%d",&n);tot=0;
        for (i=1;i<=n;++i)
          for (j=1;j<=n;++j){scanf("%d",&m);if(m) add(i,j);}
        for (tot=1;tot<=n;++tot) if(!find(tot)) break;
        if (tot<=n) printf("No\n");
        else printf("Yes\n");
    }
}
View Code

 

cogs746 骑士共存

题目大意:在n*n的棋盘上放马,要求互不攻击,求最多能放多少个。

思路:为了构建二分图,我们把棋盘黑白染色,黑色格子只能向白色连边(马跳跃的性质),然后找最大匹配,用总点数(无障碍的)-最大匹配数就是答案。

注意:二分图,把棋盘分成两部分的思想。(这就是二分图最大独立子集问题,证明比较简单,我们可以看做删掉最少的点,使剩下的点之间没有连边,即用最小覆盖的答案,而最小覆盖又等于最大匹配,所以就可以表示成总点数-最大匹配数了。)

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int db[201][201],match[40001]={0},next[700000]={0},point[40001]={0},en[700000]={0},tot=0,
    dx[8]={-2,-2,-1,-1,1,1,2,2},dy[8]={-1,1,-2,2,-2,2,-1,1};
bool visit[40001]={false};
void add(int st,int enn)
{
    ++tot;next[tot]=point[st];point[st]=tot;en[tot]=enn;
    ++tot;next[tot]=point[enn];point[enn]=tot;en[tot]=st;
}
bool find(int x)
{
    int i;
    for (i=point[x];i!=0;i=next[i])
    {
        if (!visit[en[i]])
        {
            visit[en[i]]=true;
            if (!match[en[i]]||find(match[en[i]]))
            {
                match[en[i]]=x;return true;
            }
        }
    }
    return false;
}
int main()
{
    int i,j,n,m,totd=0,x,y,t,ans=0;
    scanf("%d%d",&n,&m);
    for (i=1;i<=n;++i)
      for (j=1;j<=n;++j)
          db[i][j]=++totd;
    for (i=1;i<=m;++i)
    {
        scanf("%d%d",&x,&y);
        db[x][y]=0;
    }
    for (i=1;i<=n;++i)
      for (j=1;j<=n;++j)
      {
          if (db[i][j]&&((i+j)%2==0))
          {
              for (t=0;t<8;++t)
              {
                  x=i+dx[t];y=j+dy[t];
                  if (x<1||x>n) continue;
                  if (y<1||y>n) continue;
                  if (!db[x][y]) continue;
                  add(db[i][j],db[x][y]);
              }
          }
      }
    for (i=1;i<=n;++i)
      for (j=1;j<=n;++j)
      {
            if (!db[i][j]||(i+j)%2==1) continue;
          memset(visit,false,sizeof(visit));
            if (find(db[i][j])) ++ans;
      }
    ans=totd-ans-m;
    printf("%d\n",ans);
}
View Code

 

bzoj3175 攻击装置

题目大意:在一个有障碍的网格中放马,求互不攻击最多的个数。

思路:同上,二分图最大独立子集。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxm 205
#define maxe 1000000
using namespace std;
int dx[8]={-1,-2,1,2,-1,-2,1,2},dy[8]={-2,-1,-2,-1,2,1,2,1},bi[maxm][maxm]={0},cnt=0,
    point[maxe]={0},next[maxe]={0},en[maxe]={0},tot=0,visit[maxm*maxm]={0},
    match[maxm*maxm]={0},map[maxm][maxm]={0};
char in(){
    char ch;
    while(scanf("%c",&ch)==1)
        if (ch>='0'&&ch<='1') return ch;
}
void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;}
bool find(int u){
    int i,j,v;
    for (i=point[u];i;i=next[i]){
        if (visit[v=en[i]]==tot) continue;
        visit[v]=tot;
        if (!match[v]||find(match[v])){
            match[v]=u;return true;
        }
    }return false;
}
int main(){
    int n,i,j,k,x,y,ans=0;char ch;
    scanf("%d",&n);
    for (i=1;i<=n;++i)
      for (j=1;j<=n;++j){
          ch=in();map[i][j]=ch-'0';
          if (!map[i][j]) bi[i][j]=++cnt;
      }for (i=1;i<=n;++i)
      for (j=1;j<=n;++j){
          if (map[i][j]) continue;
        for(k=0;k<8;++k){
              x=i+dx[k];y=j+dy[k];
              if (x<1||x>n||y<1||y>n||map[x][y]) continue;
              add(bi[i][j],bi[x][y]);
          }
      }for (tot=0,i=1;i<=n;++i)
      for (j=1;j<=n;++j){
          if (map[i][j]||((i+j)%2)) continue;
          ++tot;if (find(bi[i][j])) ++ans;
      }printf("%d\n",cnt-ans);
}
View Code

 

bzoj1562 序列变换

题目大意:定义dis(i,j)=min(abs(i-j),n-abs(i-j)),求一个序列0~n-1的置换Ti,使得dis(i,Ti)为给定值。

思路:每个点一定对应着两个点,所以可以二分图跑一下,对于输出方案的字典序最小,又变成了贪心的时候对应位置的字典序最大,那么就是二分图的时候先选终点小的,从大的点开始跑的话,如果要更改的话,大的点只会变大,所以就是字典序最小了(其实做的时候是把这些顺序换一下,硬凑出来的。)(还有就是一定要考虑终点的大小,因为%了之后+不一定大、-不一定小)。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxm 20005
using namespace std;
int point[maxm]={0},en[maxm],next[maxm]={0},match[maxm]={0},visit[maxm]={0},tot=0,ans[maxm];
void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;}
bool judge(int u,int t){
    int i,j,v;
    for (i=point[u];i;i=next[i]){
        if (visit[v=en[i]]==t) continue;
        visit[v]=t;
        if (!match[v]||judge(match[v],t)){
            match[v]=u;return true;
        }
    }return false;
}
int main(){
    int i,j,n,x,a,b;scanf("%d",&n);
    for (i=0;i<n;++i){
        scanf("%d",&x);
        a=(i-x+n)%n;b=(i+x)%n;
        if (a<b) swap(a,b);
        add(i,a);add(i,b);
    }for (j=0,i=n-1;i>=0;--i) if (judge(i,i+1)) ++j;
    if (j<n) printf("No Answer\n");
    else{
        for (i=0;i<n;++i) ans[match[i]]=i;
        for (i=0;i<n-1;++i) printf("%d ",ans[i]);
        printf("%d\n",ans[n-1]);
    }
}
View Code

 

bzoj4276 Bajtman i Okragly Robin

题目大意:已知n个强盗,每个强盗会在ai~bi某一秒时间里偷ci金币,可以在每一秒内阻止一个强盗偷金币,问最多能阻止多少钱。

思路:感觉像一个最大完美匹配,但是不会具体的算法,就用贪心+匈牙利做的。先按强盗偷的钱排降序,两排点是强盗和时间,相应的连边(n^2级别吧),然后匈牙利,能匹配就给答案加上这个值。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxm 10005
#define maxe 25000005
using namespace std;
struct use{
    int ai,bi,ci;
}rob[maxm];
int point[maxm]={0},next[maxe]={0},en[maxe]={0},tot=0,match[maxm]={0},visit[maxm]={0};
int cmp(const use&x,const use&y){return x.ci>y.ci;}
void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;}
bool find(int u,int kk){
    int i,j,v;
    for (i=point[u];i;i=next[i])
        if (visit[v=en[i]]!=kk){
            visit[v]=kk;
            if (!match[v]||find(match[v],kk)){
                match[v]=u;return true;
            }
        }return false;
}
int main(){
    int n,i,j,ans=0;scanf("%d",&n);
    for (i=1;i<=n;++i) scanf("%d%d%d",&rob[i].ai,&rob[i].bi,&rob[i].ci);
    sort(rob+1,rob+n+1,cmp);
    for (i=1;i<=n;++i)
      for (j=rob[i].ai;j<rob[i].bi;++j) add(i,j);
    for (i=1;i<=n;++i)
        if (find(i,i)) ans+=rob[i].ci;
    printf("%d\n",ans);
}
View Code

 

KM

bzoj1937 最小生成树(!!!

题目大意:给定一个联通的无向图,有边权,其中有n-1条树边,现在希望改变边的权值,使树边一定出现在最小生成树中(可以有多棵最小生成树),求最小的修改的和。

思路:考虑对边的修改,设每条边改变的量是di(di>=0),非树边的权值一定是ci+di,树边的权值一定是ci-di,同时对于非树边i覆盖的树边j一定满足ci+di>=cj-dj,所以di+dj>=cj-ci,这个式子非常像km算法中lx+ly>=wxy,所以可以用km算法求解,对于边建点,树边i向能覆盖的非树边j连边cj-ci。复杂度是O(m^4)(实际效果非常好,可优化到O(m^3)),这样还可以求出修改的方案。

还有一种O(n^3)的方法,不能求方案。

#include<iostream> 
#include<cstdio> 
#include<cstring> 
#include<algorithm> 
#define N 65 
#define M 805 
#define inf 2100000000 
using namespace std; 
struct use{int x,y,c,k;}ed[M]; 
int w[M][M]={0},gi[M][M]={0},match[M],lx[M],ly[M],n,m,mi[M][M]={0}; 
bool si[M],ti[M]; 
int cmp(const use&x,const use&y){return x.k>y.k;} 
bool dfs(int u,int v,int fa,int id){ 
    int i; 
    if (u==v) return true; 
    for (i=1;i<=n;++i){ 
        if (i==fa||!gi[u][i]) continue; 
        if (dfs(i,v,u,id)){ 
            w[gi[u][i]][id]=ed[gi[u][i]].c-ed[id].c; 
            return true; 
        } 
    }return false;} 
bool find(int u){ 
    int i; 
    si[u]=true; 
    for (i=1;i<=m;++i) 
      if (lx[u]+ly[i]==w[u][i]&&!ti[i]){ 
        ti[i]=true; 
        if (!match[i]||find(match[i])){ 
            match[i]=u;return true; 
        } 
      }return false;} 
void updata(){ 
    int mn,i,j;mn=inf; 
    for (i=1;i<=m;++i) if (si[i]) 
      for (j=1;j<=m;++j) if (!ti[j]) 
        mn=min(mn,lx[i]+ly[j]-w[i][j]); 
    for (i=1;i<=m;++i){ 
        if (si[i]) lx[i]-=mn; 
        if (ti[i]) ly[i]+=mn; 
    } 
} 
void km(){ 
    int i,j; 
    for (i=1;i<=m;++i){ 
        match[i]=lx[i]=ly[i]=0; 
        for (j=1;j<=m;++j) lx[i]=max(lx[i],w[i][j]); 
    }for (i=1;i<=m;++i) 
        while(1){ 
            for (j=1;j<=m;++j) si[j]=ti[j]=0; 
            if (find(i)) break;else updata(); 
        } 
} 
int main(){ 
    int i,u,v,cc,ans=0; 
    scanf("%d%d",&n,&m); 
    for (i=1;i<=m;++i){ 
        scanf("%d%d%d",&u,&v,&cc); 
        ed[i]=(use){u,v,cc,0};mi[u][v]=mi[v][u]=i; 
    }for (i=1;i<n;++i){ 
        scanf("%d%d",&u,&v); 
        ed[mi[u][v]].k=1; 
    }sort(ed+1,ed+m+1,cmp); 
    for (i=1;i<=m;++i){ 
        if (ed[i].k) gi[ed[i].x][ed[i].y]=gi[ed[i].y][ed[i].x]=i; 
        else dfs(ed[i].x,ed[i].y,0,i); 
    }km(); 
    for (i=1;i<n;++i) ans+=lx[i]; 
    for (i=n;i<=m;++i) ans+=ly[i]; 
    printf("%d\n",ans); 
}
View Code

 

bzoj3571 画框(!!!

题目大意:1~n个元素1配对1~n个元素2,要求一一对应,并且已知每个元素1和元素2配对的权值ai,j、bi,j,求min sigma ai,pi * sigma bi,pi(pi表示i这个元素1对应的元素2)。

思路:分治+km。最小乘积生成树模型,将sigma aij和sigma bij看做横纵坐标,最优答案的乘积看做双曲线y=k/x的k,不断逼近最优曲线就可以了。先求点A表示sigma aij最小的,点B表示sigma bij最小的,这是一维的km;然后求离AB最远的且在AB下方的点C,设AB:ax+by+c=0,就是求最大的|a*C.x+b*C.y+c|/sqrt(a^2+b^2),a、b、c都是定值,所以就是求最大的a*ai,j+b*bi,j,这也是一维的km。考虑到C在AB下面,所以a*C.x+b*C.y+c>0,所以当a*C.x+b*C.y+c<=0或者A、B和C相等了就退出分治。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 75
#define inf 2100000000
using namespace std;
struct use{
    int x,y;
    bool operator==(const use&a){return x==a.x&&y==a.y;}
};
int si[N]={0},ti[N]={0},ai[N][N],bi[N][N],wi[N][N],sla[N],lx[N],ly[N],n,tt=0,match[N],ans;
bool find(int u){
    int i;si[u]=tt;
    for (i=1;i<=n;++i){
        if (ti[i]==tt) continue;
        if (lx[u]+ly[i]==wi[u][i]){
            ti[i]=tt;
            if (!match[i]||find(match[i])){
                match[i]=u;return true;
            }
        }else sla[i]=min(sla[i],lx[u]+ly[i]-wi[u][i]);
    }return false;
}
void updata(){
    int i,mn;mn=inf;
    for (i=1;i<=n;++i)
      if (ti[i]!=tt) mn=min(mn,sla[i]);
    for (i=1;i<=n;++i){
        if (si[i]==tt) lx[i]-=mn;
        if (ti[i]==tt) ly[i]+=mn;
        else sla[i]-=mn;
    }
}
use km(){
    int i,j;use ci;
    for (i=1;i<=n;++i){
        lx[i]=ly[i]=match[i]=0;
        for (j=1;j<=n;++j) lx[i]=max(lx[i],wi[i][j]);
    }for (i=1;i<=n;++i){
        memset(sla,127,sizeof(sla));
        while(1){
            ++tt;
            if (find(i)) break;
            else updata();
        }
    }ci.x=ci.y=0;
    for (i=1;i<=n;++i){
        ci.x+=ai[match[i]][i];
        ci.y+=bi[match[i]][i];
    }ans=min(ans,ci.x*ci.y);
    return ci;
}
void work(use l,use r){
    int i,j,aa,bb,cc;
    aa=r.y-l.y;bb=l.x-r.x;cc=-(aa*l.x+bb*l.y);
    for (i=1;i<=n;++i)
      for (j=1;j<=n;++j) wi[i][j]=aa*ai[i][j]+bb*bi[i][j];
    use mid=km();
    if (l==mid||r==mid) return;
    work(l,mid);work(mid,r);
}
int main(){
    int i,j,t;use l,r;scanf("%d",&t);
    while(t--){
        scanf("%d",&n);ans=inf;
        for (i=1;i<=n;++i)
          for (j=1;j<=n;++j) scanf("%d",&ai[i][j]);
        for (i=1;i<=n;++i)
          for (j=1;j<=n;++j) scanf("%d",&bi[i][j]);
        for (i=1;i<=n;++i)
          for (j=1;j<=n;++j) wi[i][j]=-ai[i][j];
        l=km();
        for (i=1;i<=n;++i)
          for (j=1;j<=n;++j) wi[i][j]=-bi[i][j];
        r=km();work(l,r);
        printf("%d\n",ans);
    }
}
View Code

 

其他

poj1112 Team Them Up!

题目大意:已知一个有向图,分成大小尽量接近的两部分,保证每部分中的点都互相直接连通。

思路:对于不能在一个部分的点连边,如果是二分图就是有解(!!!)。因为这样可能有很多个二分图,所以可以背包dp一下求尽量接近,记录转移,输出方案。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 105
using namespace std;
int co[N]={0},n,m,va[N][2],cc[3]={0},id[N][3][N],gi[N][N];
bool mp[N][N],ed[N][N],fi[N][N]={false};
bool dfs(int u,int c){
    int i;
    if (co[u]) return (co[u]==c);
    co[u]=c;id[m][c][++cc[c]]=u;
    for (i=1;i<=n;++i)
        if (ed[u][i])
            if (!dfs(i,3-c)) return false;
    return true;
}
int main(){
    int v,i,j,k,nn,nt;scanf("%d",&n);
    memset(mp,false,sizeof(mp));
    memset(ed,false,sizeof(ed));
    for (i=1;i<=n;++i)
        for (scanf("%d",&v);v;scanf("%d",&v)) mp[i][v]=true;
    for (i=1;i<=n;++i)
        for (j=1;j<=n;++j)
            if (i!=j&&(!mp[i][j]||!mp[j][i])) ed[i][j]=true;
    for (i=1;i<=n;++i)
        if (!co[i]){
            cc[1]=cc[2]=0;
            ++m;
            if (!dfs(i,1)) break;
            va[m][0]=cc[1];
            va[m][1]=cc[2];
        }
    if (i<=n) printf("No solution\n");
    else{
        fi[0][0]=true;
        nn=n/2;
        for (i=1;i<=m;++i)
            for (k=0;k<2;++k)
                for (j=0;j+va[i][k]<=nn;++j)
                    if (fi[i-1][j]){
                        fi[i][j+va[i][k]]=true;
                        gi[i][j+va[i][k]]=k;
                    }
        for (i=nn;i;--i)
            if (fi[m][i]) break;
        nt=i;
        memset(co,0,sizeof(co));
        for (i=m;i;--i){
            for (j=1;j<=va[i][gi[i][nt]];++j)
                co[id[i][gi[i][nt]+1][j]]=1;
            nt-=va[i][gi[i][nt]];
        }cc[0]=cc[1]=0;
        for (i=1;i<=n;++i) ++cc[co[i]];
        for (i=0;i<2;++i){
            printf("%d",cc[i]);
            for (j=1;j<=n;++j) if (co[j]==i) printf(" %d",j);
            printf("\n");
        }
    }
}
View Code

 

posted @ 2016-02-15 19:49  Rivendell  阅读(659)  评论(0编辑  收藏  举报