[集合DP] UVa1252 20个问题(预处理)(技巧枚举)

题目

这里写图片描述
(题目感觉好萌呀)

思路

(本题较难)
首先,设“心里想的物体”为W,集合s为已经询问过的特征,集合a为s中确定W具备的特征,其中一定有 a s 。设d(s,a)为已经询问了特征集s,且确定W具备的为a时,还需要询问的最小次数。这里考虑如何决策:
决策为下一次提问的特征k,k [0,m)且k不在s中,则下一次提问k时的询问次数是: m a x { d ( s + { k } , a + { k } ) ,   d ( s + { k } , a ) } + 1 。状态转移时需要枚举所有的k,然后找出询问次数的最小值即可。


此处为了避免在dp的时候判断,要先预处理出cnt[s][a],即在s和a的情况下,符合的物体的数量。
显然此处的预处理需要枚举,最容易想到的枚举是,遍历s,a(a作为s的子集),然后再遍历n寻找符合s和a的物体。这个枚举的时间复杂度在图的色数里面谈过, O ( n 3 n ) ,比较大,可以采用技巧枚举
遍历s和n,直接根据物体来填cnt数组:

_for(s, 0, (1 << m))
    _for(i, 0, n)
        cnt[s][s & st[i]]++;

这样时间复杂度就控制在了 O ( n 2 n )


1.状态定义:d(s,a),已经询问了特征集s,且确定W具备的特征为a集时,还需要询问的最小次数。
2.边界:当一个物体“具备集合a中的所有特征,不具备集合s-a中的所有特征”时,d(s,a) = 0
3.答案:d(0,0)
4.状态转移方程:

d ( s , a ) = m i n { m a x { d ( s + { k } , a + { k } ) ,   d ( s + { k } , a ) } + 1   | k [ 0 , m ) , k s   }

5.时间复杂度: O ( m 3 m )
(在预处理时还有 O ( n 2 n ) 的复杂度,但在本题规模中可以忽略不计)


再次强调位运算的优先级问题,下面代码可以这么写

if ((s & (1 << k)) == 0)

但不能这么写

if (s & (1 << k) == 0)

位运算的优先级甚至比==还低。

代码

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;

const int INF = 1000;
const int maxn = 128 + 2;
const int maxm = 11 + 1;
int n, m, st[maxn], cnt[1 << maxm][1 << maxm], d[1 << maxm][1 << maxm];

int dp(int s, int a) {
    if (cnt[s][a] == 1) return 0;
    if (cnt[s][a] == 0) return INF;

    int &ans = d[s][a];
    if (ans != -1) return ans;
    ans = INF;

    _for(k, 0, m)
        if ((s & (1 << k)) == 0) {  // &的优先级甚至比==还小,不能写成 if (s & (1 << k) == 0)
            ans = min(ans, max(dp(s | (1 << k), a | (1 << k)), dp(s | (1 << k), a))+1);
        }
    return ans;
}

int main() {
    while (scanf("%d%d", &m, &n) == 2 && m && n) {
        char s[100];
        int code;
        _for(i, 0, n) {
            scanf("%s", s);
            code = 0;
            _for(j, 0, m)
                if (s[m-j-1] == '1')
                    code |= (1 << j);
            st[i] = code;
        }
        memset(cnt, 0, sizeof(cnt));
        _for(s, 0, (1 << m))
            _for(i, 0, n)
                cnt[s][s & st[i]]++;  // a表示已经询问的s中具备的

        memset(d, -1, sizeof(d));
        printf("%d\n", dp(0, 0));
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/icecab/article/details/80998456
今日推荐