首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

ZOJ-3305* 二进制子集区划

2012-10-26 
ZOJ-3305* 二进制子集划分3305:有N中调料,M种配方。调料每种只能用一次,求能实现多少种配方。即统计能划分出

ZOJ-3305* 二进制子集划分
3305:有N中调料,M种配方。调料每种只能用一次,求能实现多少种配方。
即统计能划分出多少备选子集。


思路:本题代码参考自http://akheyun.blog.163.com/blog/static/13824927620102183300293/
核心思想是二进制表现。
x = (x - 1) & st 实现了子集遍历
比如 st=1110
1101 & 1110 = 1100
1011 & 1110 = 1010
1001 & 1110 = 1000
0111 & 1110 = 0110
0101 & 1110 = 0100
0011 & 1110 = 0010
0001 & 1110 = 0000

联想起求一个数的二进制中1有多少。
用到 x = (x-1) & x  这每次会使一个1变为0 用时可以小于O(n)

#include<iostream>using namespace std;#include<stdio.h>#include<memory.h>const int M = 1 << 16 + 1;bool a[M];int dp[M];int solve(int st){         int i;         if (dp[st] != -1) return dp[st]; //已经计算过,直接返回         dp[st] = 0;         for (int x = st; x != 0; x = (x - 1) & st)         {               //不使用x能达到的配方数和使用x后配方数的最大值               if (a[x])                   dp[st] = max(dp[st], solve(st - x) + 1);         }         return dp[st];} int main (){         int i, j, n, m, k, p;         while (scanf("%d%d", &n, &m) != EOF)         {               memset(a, false, sizeof(a));               memset(dp, -1, sizeof(dp));               for (i = 0; i < m; i++)               {                    scanf("%d", &k);                    j = 0;                    while (k--)                    {                          scanf("%d", &p);                          j += (1 << (p - 1)); //每种配方用2进制表示                    }                    a[j] = 1;               }               //初始全为1传入               printf("%d\n", solve((1 << n) - 1));         }}

热点排行