zoukankan      html  css  js  c++  java
  • 二分图

    匈牙利算法

    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
    ",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
    ");
            else printf("Yes
    ");
        }
    }
    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
    ",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
    ",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
    ");
        else{
            for (i=0;i<n;++i) ans[match[i]]=i;
            for (i=0;i<n-1;++i) printf("%d ",ans[i]);
            printf("%d
    ",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
    ",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
    ",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
    ",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
    ");
        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("
    ");
            }
        }
    }
    View Code
  • 相关阅读:
    WPF中Name和x:Name
    依赖注入(Dependency Injection)
    SQL复制表操作
    奇异值分解和聚类分析操作流程
    奇异值分解(SVD)
    js读取本地txt文件中的json数据
    Python对字典(directory)按key和value排序
    PowerDesigner导入java类生成类图
    python-Levenshtein几个计算字串相似度的函数解析
    编辑距离算法(Levenshtein)
  • 原文地址:https://www.cnblogs.com/Rivendell/p/5191228.html
Copyright © 2011-2022 走看看