这个题比较有意思,我单独开一个来写一写
题意:
题意:迈克有一台可以净化奶酪的机器,用二进制表示净化的奶酪的编号。但是,在某些二进制串中可能包含有‘*'。例如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;
}