标签:
Time Limit: 2000MS | Memory Limit: 65536K | |
Total Submissions: 5027 | Accepted: 1455 |
Description
Input
Output
Sample Input
3 3 *01 100 011 0 0
Sample Output
2
Source
题意:
题意:迈克有一台可以净化奶酪的机器,用二进制表示净化的奶酪的编号。但是,在某些二进制串中可能包含有‘*‘。例如01*100,‘*‘其实就代表可以取0,1两种情况--> 010100 和011100。现在由于迈克不小心,他以同样的方式弄脏了某些奶酪,问你最少用多少次操作就可以把弄脏的奶酪全净化好。(没有被弄脏过的奶酪不能净化。弄脏过的奶酪可以多次净化。)
思路:
也就是给你一些不同的(判重之后)二进制串,每个串可以通过1次操作净化,也可以把两个只有1位不同的串通过1次操作联合净化.要我们求最少的操作次数.
我们把所有串按其中1的个数和是奇还是偶分成左右两个点集.
对于任意两个串,如果他们只有1位不同,那么就在他们之间连接一条无向边.(这两个串一定分别属于不同的点集)
由于串的总数是固定的,且一个串可以通过单独净化也可以通过联合净化.而我们向让净化的次数最少,我们自然想联合净化(即一次可以净化两个串)的次数尽量多了. 那么我们最多可以进行多少次联合净化呢? 这个数值==我们建立二分图的最大匹配边数.(想想是不是,因为一个串最多只能被净化一次)
假设总的不同串有n个,我们建立二分图的最大匹配数(即联合净化最大次数)为ans,那么我们总共需要n-ans次净化即可.(想想为什么)
当然本题也可以不用把串特意分成左右点集(本程序实现就是用的这种方式:未分左右点集),我们只需要把原图翻倍,然后求翻倍图的最大匹配数ans,最后用n-ans/2即可.
这个题中有很多位运算的有意思的东西
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N=3000+50; int uN,vN; int g[N][N]; int link[N]; int used[N]; int num[N]; int dfs(int u){ for(int v=0;v<vN;v++)//顶点编号从0开始的 if(g[u][v]&&!used[v]){ used[v]=1; if(link[v]==-1||dfs(link[v])){ link[v]=u; return 1; } } return 0; } int hungary(){ int res=0; memset(link,-1,sizeof(link)); for(int 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)==2&&(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; 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; }
标签:
原文地址:http://www.cnblogs.com/shenben/p/5634144.html