📜  位掩码和动态规划 |设置 1(计算为每个人分配唯一上限的方法)

📅  最后修改于: 2021-09-17 07:13:31             🧑  作者: Mango

考虑以下问题陈述。

有 100 种不同类型的帽子,每种帽子都有一个从 1 到 100 的唯一 ID。此外,有“n”个人,每个人都有一组可变数量的帽子。有一天,所有这些人都决定戴着帽子参加派对,但为了看起来很独特,他们决定他们都不会戴相同类型的帽子。因此,计算排列或方式的总数,以便他们中没有人戴相同类型的帽子。

约束条件:1 <= n <= 10 示例:

The first line contains the value of n, next n lines contain collections 
of all the n persons.
Input: 
3
5 100 1     // Collection of the first person.
2           // Collection of the second person.
5 100       // Collection of the third person.

Output:
4
Explanation: All valid possible ways are (5, 2, 100),  (100, 2, 5),
            (1, 2, 5) and  (1, 2, 100)

因为,方式的数量可能很大,所以输出模 1000000007

我们强烈建议您将浏览器最小化,然后自己先尝试一下。
一个简单的解决方案是尝试所有可能的组合。首先从第一个集合中选择第一个元素,将其标记为已访问并在剩余集合中重复出现。它基本上是一个基于回溯的解决方案。

更好的解决方案是使用 Bitmasking 和 DP 。让我们首先介绍位掩码。

什么是位掩码?
假设我们有一个从 1 到 N 编号的元素集合。如果我们想表示这个集合的一个子集,那么它可以用 N 位的序列进行编码(我们通常称这个序列为“掩码”)。在我们选择的子集中,第 i 个元素属于它当且仅当掩码的第 i 位被设置,即它等于 1。例如,掩码 10000101 表示集合 [1…8 ] 由元素 1、3 和 8 组成。我们知道对于一组 N 个元素,总共有 2 N个子集,因此可能有 2 N 个掩码,一个代表每个子集。实际上,每个掩码都是用二进制表示法编写的整数。

我们的主要方法是为每个掩码(因此也为每个子集)分配一个值,从而使用已计算掩码的值计算新掩码的值。通常我们的主要目标是计算完整集的值/解决方案,即掩码 11111111。通常,为了找到子集 X 的值,我们以各种可能的方式删除一个元素,并使用获得的子集 X’ 1 , X’ 的值2 … ,X’ k来计算 X 的值/解。这意味着 X’ i的值必须已经计算过,所以我们需要建立一个将考虑掩码的顺序。很容易看出自然排序可以:按照相应数字的递增顺序遍历掩码。此外,我们有时从空子集 X 开始,我们以各种可能的方式添加元素,并使用获得的子集 X’ 1 , X’ 2 … , X’ k的值来计算 X 的值/解。

我们主要在掩码上使用以下符号/操作:
bit(i, mask) – 掩码的第 i 位
count(mask) – 掩码中非零位的数量
first(mask) – 掩码中最低非零位的编号
set(i, mask) – 设置掩码中的第 i 位
check(i, mask) – 检查掩码中的第 i 位

这个问题是如何使用 Bitmasking + DP 解决的?
这个想法是利用最多 10 人的事实。所以我们可以使用一个整数变量作为位掩码来存储哪些人戴帽子,哪些人没有。

Let i be the current cap number (caps from 1 to i-1 are already 
processed). Let integer variable mask indicates that the persons w
earing and not wearing caps.  If i'th bit is set in mask, then 
i'th person is wearing a cap, else not.

             // consider the case when ith cap is not included 
                     // in the arrangement
countWays(mask, i) = countWays(mask, i+1) +             
                    
                    // when ith cap is included in the arrangement
                    // so, assign this cap to all possible persons 
                    // one by one and recur for remaining persons.
                    ∑ countWays(mask | (1 << j), i+1)
                       for every person j that can wear cap i 
 
Note that the expression "mask | (1 << j)" sets j'th bit in mask.
And a person can wear cap i if it is there in the person's cap list
provided as input.

如果我们画出完整的递归树,我们可以观察到很多子问题被一次又一次地解决了。所以我们使用动态规划。使用表 dp[][] 使得在每个条目 dp[i][j] 中,i 是掩码,j 是上限编号。

因为我们想要访问所有可以戴给定帽子的人,所以我们使用向量数组 capList[101]。值 capList[i] 表示可以戴帽 i 的人员列表。

下面是上述想法的实现。

C/C++
// C++ program to find number of ways to wear hats
#include
#define MOD 1000000007
using namespace std;
  
// capList[i]'th vector contains the list of persons having a cap with id i
// id is between 1 to 100 so we declared an array of 101 vectors as indexing
// starts from 0.
vector capList[101];
  
// dp[2^10][101] .. in dp[i][j], i denotes the mask i.e., it tells that
// how many and which persons are wearing cap. j denotes the first j caps
// used. So, dp[i][j] tells the number ways we assign j caps to mask i
// such that none of them wears the same cap
int dp[1025][101];
  
// This is used for base case, it has all the N bits set
// so, it tells whether all N persons are wearing a cap.
int allmask;
  
// Mask is the set of persons, i is cap-id (OR the 
// number of caps processed starting from first cap).
long long int countWaysUtil(int mask, int i)
{
    // If all persons are wearing a cap so we
    // are done and this is one way so return 1
    if (mask == allmask) return 1;
  
    // If not everyone is wearing a cap and also there are no more
    // caps left to process, so there is no way, thus return 0;
    if (i > 100) return 0;
  
    // If we already have solved this subproblem, return the answer.
    if (dp[mask][i] != -1) return dp[mask][i];
  
    // Ways, when we don't include this cap in our arrangement
    // or solution set.
    long long int ways = countWaysUtil(mask, i+1);
  
    // size is the total number of persons having cap with id i.
    int size = capList[i].size();
  
    // So, assign one by one ith cap to all the possible persons
    // and recur for remaining caps.
    for (int j = 0; j < size; j++)
    {
        // if person capList[i][j] is already wearing a cap so continue as
        // we cannot assign him this cap
        if (mask & (1 << capList[i][j])) continue;
  
        // Else assign him this cap and recur for remaining caps with
        // new updated mask vector
        else ways += countWaysUtil(mask | (1 << capList[i][j]), i+1);
        ways %= MOD;
    }
  
    // Save the result and return it.
    return dp[mask][i] = ways;
}
  
// Reads n lines from standard input for current test case
void countWays(int n)
{
    //----------- READ INPUT --------------------------
    string temp, str;
    int x;
    getline(cin, str);  // to get rid of newline character
    for (int i=0; i> temp)
        {
            stringstream s;
            s << temp;
            s >> x;
  
            // add the ith person in the list of cap if with id x
            capList[x].push_back(i);
        }
    }
    //----------------------------------------------------
  
    // All mask is used to check whether all persons
    // are included or not, set all n bits as 1
    allmask = (1 << n) - 1;
  
    // Initialize all entries in dp as -1
    memset(dp, -1, sizeof dp);
  
    // Call recursive function count ways
    cout << countWaysUtil(0, 1) << endl;
}
  
// Driver Program
int main()
{ 
     int n;   // number of persons in every test case
     cin >> n;
     countWays(n);
     return 0;
}


Java
// Java program to find number of ways to wear hats
  
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Vector;
  
class Test
{
    static final int MOD = 1000000007;
      
    // for input
    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
      
    // capList[i]'th vector contains the list of persons having a cap with id i
    // id is between 1 to 100 so we declared an array of 101 vectors as indexing
    // starts from 0.
    static Vector capList[] = new Vector[101];
      
       
    // dp[2^10][101] .. in dp[i][j], i denotes the mask i.e., it tells that
    // how many and which persons are wearing cap. j denotes the first j caps
    // used. So, dp[i][j] tells the number ways we assign j caps to mask i
    // such that none of them wears the same cap
    static int dp[][] = new int[1025][101];
       
    // This is used for base case, it has all the N bits set
    // so, it tells whether all N persons are wearing a cap.
    static int allmask;
       
    // Mask is the set of persons, i is cap-id (OR the 
    // number of caps processed starting from first cap).
    static long countWaysUtil(int mask, int i)
    {
        // If all persons are wearing a cap so we
        // are done and this is one way so return 1
        if (mask == allmask) return 1;
       
        // If not everyone is wearing a cap and also there are no more
        // caps left to process, so there is no way, thus return 0;
        if (i > 100) return 0;
       
        // If we already have solved this subproblem, return the answer.
        if (dp[mask][i] != -1) return dp[mask][i];
       
        // Ways, when we don't include this cap in our arrangement
        // or solution set.
        long ways = countWaysUtil(mask, i+1);
       
        // size is the total number of persons having cap with id i.
        int size = capList[i].size();
       
        // So, assign one by one ith cap to all the possible persons
        // and recur for remaining caps.
        for (int j = 0; j < size; j++)
        {
            // if person capList[i][j] is already wearing a cap so continue as
            // we cannot assign him this cap
            if ((mask & (1 << capList[i].get(j))) != 0) continue;
       
            // Else assign him this cap and recur for remaining caps with
            // new updated mask vector
            else ways += countWaysUtil(mask | (1 << capList[i].get(j)), i+1);
            ways %= MOD;
        }
       
        // Save the result and return it.
        return dp[mask][i] = (int) ways;
    }
       
    // Reads n lines from standard input for current test case
    static void countWays(int n) throws Exception
    {
        //----------- READ INPUT --------------------------
        String str;
        String split[];
        int x;
                
        for (int i=0; i();
          
          
        n = Integer.parseInt(br.readLine());
        countWays(n);
    }
}
// This code is contributed by Gaurav Miglani


Python
#Python program to find number of ways to wear hats
from collections import defaultdict
  
class AssignCap:
  
    # Initialize variables
    def __init__(self):
  
            self.allmask = 0
  
            self.total_caps = 100
  
            self.caps = defaultdict(list)
  
  
    #  Mask is the set of persons, i is the current cap number.
    def countWaysUtil(self,dp, mask, cap_no):
          
        # If all persons are wearing a cap so we
        # are done and this is one way so return 1
        if mask == self.allmask:
            return 1
  
        # If not everyone is wearing a cap and also there are no more
        # caps left to process, so there is no way, thus return 0;
        if cap_no > self.total_caps:
            return 0
  
        # If we have already solved this subproblem, return the answer.
        if dp[mask][cap_no]!= -1 :
            return dp[mask][cap_no]
  
        # Ways, when we don't include this cap in our arrangement
        # or solution set
        ways = self.countWaysUtil(dp, mask, cap_no + 1)
          
        # assign ith cap one by one  to all the possible persons
        # and recur for remaining caps.
        if cap_no in self.caps:
  
            for ppl in self.caps[cap_no]:
                  
                # if person 'ppl' is already wearing a cap then continue
                if mask & (1 << ppl) : continue
                  
                # Else assign him this cap and recur for remaining caps with
                # new updated mask vector
                ways += self.countWaysUtil(dp, mask | (1 << ppl), cap_no + 1) 
  
                ways = ways % (10**9 + 7)
  
        # Save the result and return it
        dp[mask][cap_no] = ways
  
        return dp[mask][cap_no]
  
  
  
    def countWays(self,N):
  
        # Reads n lines from standard input for current test case
        # create dictionary for cap. cap[i] = list of person having
        # cap no i
        for ppl in range(N):
  
            cap_possessed_by_person = map(int, raw_input().strip().split())
  
            for i in cap_possessed_by_person:
  
                self.caps[i].append(ppl)
  
        # allmask is used to check if all persons
        # are included or not, set all n bits as 1
        self.allmask = (1 << N) -1
  
        # Initialize all entries in dp as -1
        dp = [[-1 for j in range(self.total_caps + 1)] for i in range(2 ** N)]
  
        # Call recursive function countWaysUtil
        # result will be in dp[0][1]
        print self.countWaysUtil(dp, 0, 1,)
  
#Driver Program
def main():
    No_of_people = input() # number of persons in every test case
  
    AssignCap().countWays(No_of_people)
  
  
if __name__ == '__main__':
    main()
  
# This code is contributed by Neelam Yadav


输入:

3               
5 100 1         
2               
5 100

输出:

4

如果您希望与专家一起参加现场课程,请参阅DSA 现场工作专业课程学生竞争性编程现场课程