📜  画家的分区问题| 2套(1)

📅  最后修改于: 2023-12-03 15:27:15.413000             🧑  作者: Mango

画家的分区问题 | 2套

问题描述

在平面上有 $n$ 个点,现在需要将它们分为两组。每组分别用一种颜色进行标记。同一组中的点必须两两可达(即存在一条路径可以连接它们),并且两组之间不能通过路径相互连接。求合法的方案数。

解题思路
套路解法:二分图

将所有点看做二分图的点,根据题目中给出的条件,可以得到两个条件:

  • 对于同一部分内的任意两点,它们之间一定连有一条边;
  • 对于不同部分的任意两点,它们之间一定不连有一条边。

这就是典型的二分图问题。对于二分图问题,可以使用匈牙利算法、网络流等方法求解。这里介绍一下核心思路。

首先为二分图中的每个点拆成两个点,分别表示其两种颜色(具体实现可以使用下标的奇偶性)。

对于原图中每一条边 $(u,v)$,在新图中加入一条从 $u_1$ 到 $v_2$ 的有向边,表示 $u$ 的蓝色部分到达 $v$ 的红色部分;同时加入一条从 $v_1$ 到 $u_2$ 的有向边,表示 $v$ 的蓝色部分到达 $u$ 的红色部分。

此时针对新图跑一遍最大匹配,得到的结果即为两个部分内的点分别所属的组。由于新图的点数至多是原图点数的两倍,时间复杂度为 $\mathcal{O}(n^2)$,最大匹配算法也可以用网络流等算法实现。

巧妙解法:状压DP

由于本题的数据范围比较小,可以考虑使用状态压缩的方法进行解题。

首先定义一个状态 $dp[S]$,表示前 $i$ 个点分成若干个点集,其中已经确定点集 $S$ 中的点已经分为一组,其余未知的点中一部分已经分为与 $S$ 相同的这一组,其余点还未被分组的方案总数。

具体的状态转移方程为:

$$ dp[S] = \sum\limits_{T \subseteq S | T \neq S} dp[T]\times F(T,S \backslash T) $$

其中 $T \subseteq S$ 表示 $T$ 是 $S$ 的子集($T$ 也可以为空集);$F(T,S \backslash T)$ 表示已知组别的点集 $T$ 和 $S \backslash T$ 之间不会相互连通的方案数,它可以通过状压 DP 求解得到。

由于状态数量是 $2^n$ 级别的,因此时间复杂度为 $\mathcal{O}(n^22^n)$。若使用状压 DP,需要注意一些常用的技巧,包括优化枚举的顺序、状态压缩的实现方法等。

参考代码
二分图
def dfs(u,vis,match):
    for v in range(n):
        if vis[v]: continue
        if G[u][v]:
            vis[v] = 1
            if match[v] == -1 or dfs(match[v],vis,match):
                match[v] = u
                return True
    return False

def max_match():
    match = [-1]*n
    res = 0
    for u in range(n):
        vis = [0]*n
        if dfs(u,vis,match): res += 1
    return res

n = int(input())
G = [[0]*n for i in range(n)]
for i in range(n):
    arr = list(map(int,input().split()))
    for j in range(n):
        if arr[j]: G[i][j] = 1

for i in range(n):
    for j in range(i,n):
        if G[i][j]:
            G[i][j], G[j][i] = 0, 0 
            G[i+n][j+n], G[j+n][i+n] = 1, 1

print(max_match())
状压 DP
def F(s,t):
    """
    计算组别为s、t之间的方案数
    """
    res = 1
    # 遍历所有点
    for i in range(n):
        # 第i个点未确定组别时
        if s&(1<<i) == 0 and t&(1<<i) == 0:
            # 判断是否与s、t中已知组别的点相连
            cnt = 0
            for j in range(n):
                if s&(1<<j) and G[i][j]:
                    cnt += 1
                if t&(1<<j) and G[i][j]:
                    cnt -= 1
            if cnt > 0: res += cnt
    return res

n = int(input())
G = [[0]*n for i in range(n)]
for i in range(n):
    arr = list(map(int,input().split()))
    for j in range(n):
        if arr[j]: G[i][j] = 1

dp = [0]*(1<<n)
dp[0] = 1
for s in range(1,(1<<n)-1):
    # 枚举子集
    for t in range(1,s):
        u, v = s^t, t
        if dp[t] and F(u,v) > 0:
            dp[s] += dp[t] * F(u,v)

print(dp[-1])