跳转至

题意

房子内一共有 n 位居民,房门上有 k 种锁,每种锁都对应一种钥匙。每种钥匙都有足够多,你可以将它们任意分配给居民。一个居民既可以没有钥匙,也可以得到多把钥匙。

一个村民的集合能够开锁,当且仅当这个集合中的村民掌握了 k 种钥匙中的每一种。

每个居民有一个重要度 a_i,一个村民的集合是重要的,当且仅当其中所有村民的重要度之和大于等于 m。请你求出 k 的最小值,使得存在一种分配钥匙的方案,满足任何一个重要的村民集合能够开锁,任何一个不重要的村民集合不能开锁。

解析

自然地,我们可以为每一个“不重要”的集合都新增一种钥匙,将其分发给这个“不重要”集合的补集。这样一定可以保证所有“不重要”的集合都无法打开房门;由于任何“重要”集合一定不含于任何一个“不重要”集合,则其也一定含有所有钥匙。

除此之外,如果一个“不重要”集合是另一个“不重要”集合的子集,则没必要为其新增一种钥匙,因为它的钥匙不是全集的限制已经由它的超集满足了。

这种构造方法显然是合法的,但是否是最优的?这里给出一个比较玄学的证明:

证明

设按照以上方法需要 x 种钥匙,也就是说极大不重要集合的数量为 x。若存在一种更优的方法使用少于 x 种钥匙,根据抽屉原理,必然存在两个极大不重要集合都不含同一种钥匙,它们的并集不能开门。故不存在使用少于 x 种钥匙的分配方案。

实现

#include <bits/stdc++.h>

int n, m, a[20];
long long int sum[1 << 20];
bool vist[1 << 20];

int main(){
    std::scanf("%d%d", &n, &m);
    for(int i = 0; i < n; ++i) std::scanf("%d", a + i);
    for(int s = 1; s < (1 << n); ++s){
        for(int i = 0; i < n; ++i)
            if(s & (1 << i)){
                sum[s] = sum[s ^ (1 << i)] + a[i];
                break;
            }
        if(sum[s] < m)
            for(int i = 0; i < n; ++i)
                if(s & (1 << i))
                    vist[s ^ (1 << i)] = true;
    }
    int ans = 0;
    for(int s = 1; s < (1 << n); ++s)//s=0? whether emptyset is taken into consideration is not defined.
        if(sum[s] < m && !vist[s])
            ++ans;
    std::printf("%d\n", ans);
    return 0;
}

来源

NOI.AC #2013 锁

评论