poj 2724

这个题比较有意思,我单独开一个来写一写


题意:

题意:迈克有一台可以净化奶酪的机器,用二进制表示净化的奶酪的编号。但是,在某些二进制串中可能包含有‘*'。例如01*100,'*'其实就代表可以取0,1两种情况--> 010100 和011100。现在由于迈克不小心,他以同样的方式弄脏了某些奶酪,问你最少用多少次操作就可以把弄脏的奶酪全净化好。(没有被弄脏过的奶酪不能净化。弄脏过的奶酪可以多次净化。)


思路:

也就是给你一些不同的(判重之后)二进制串,每个串可以通过1次操作净化,也可以把两个只有1位不同的串通过1次操作联合净化.要我们求最少的操作次数.

       我们把所有串按其中1的个数和是奇还是偶分成左右两个点集.

对于任意两个串,如果他们只有1位不同,那么就在他们之间连接一条无向边.(这两个串一定分别属于不同的点集)

       由于串的总数是固定的,且一个串可以通过单独净化也可以通过联合净化.而我们向让净化的次数最少,我们自然想联合净化(即一次可以净化两个串)的次数尽量多了. 那么我们最多可以进行多少次联合净化呢? 这个数值==我们建立二分图的最大匹配边数.(想想是不是,因为一个串最多只能被净化一次)

      假设总的不同串有n个,我们建立二分图的最大匹配数(即联合净化最大次数)为ans,那么我们总共需要n-ans次净化即可.(想想为什么)

       当然本题也可以不用把串特意分成左右点集(本程序实现就是用的这种方式:未分左右点集),我们只需要把原图翻倍,然后求翻倍图的最大匹配数ans,最后用n-ans/2即可.



这个题中有很多位运算的有意思的东西


详见代码

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
#include<cmath>

using namespace std;
//顶点编号从0开始的
const int MAXN=3000+50;
int uN,vN;//u,v数目
int g[MAXN][MAXN];
int linker[MAXN];
bool used[MAXN];
int num[MAXN];
bool dfs(int u)//从左边开始找增广路径
{
    int v;
    for(v=0;v<vN;v++)//这个顶点编号从0开始,若要从1开始需要修改
      if(g[u][v]&&!used[v])
      {
          used[v]=true;
          if(linker[v]==-1||dfs(linker[v]))
          {//找增广路,反向
              linker[v]=u;
              return true;
          }
      }
    return false;//这个不要忘了,经常忘记这句
}
int hungary()//xiongyali
{
    int res=0;
    int u;
    memset(linker,-1,sizeof(linker));
    for(u=0;u<uN;u++)
    {
        memset(used,0,sizeof(used));
        if(dfs(u)) res++;
    }
    return res;
}
//******************************************************************************/



int main()
{
    int n,m;
    char str[50];

     while(~scanf("%d%d",&n,&m)&&(m+n))
     {
         memset(g,0,sizeof(g));
         memset(num,0,sizeof(num));
         int cnt=0;
         while(m--){
                cnt++;
            scanf("%s",&str);
            int pos=-1;
            for(int i=0;i<n;i++){
                if(str[i]=='*'){pos=i;continue;}
                num[cnt]|=(str[i]-'0')<<i;
            }
            if(pos!=-1){
                    cnt++;
                    num[cnt]=(num[cnt-1]|(1<<pos));
            }
         }
         sort(num+1,num+cnt+1);
         num[0]=-1;
         int i,j;
         for (j=0,i=1;i<=cnt;i++)
         {
            if (num[j]!=num[i])
                num[++j] = num[i];
         }
         cnt=j;
         //cout<<cnt<<endl;
         for(i=1;i<=cnt;i++)
         for(j=1;j<=cnt;j++){
            int c=num[i]^num[j];
            if(c&&((c&(c-1))==0))g[num[i]][num[j]]=1;
         }
         uN=vN=2050;

         int ans=cnt-hungary()/2;
         printf("%d\n",ans);
     }
     return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值