求助:无法理解CodeChef题目STR题解中的动态规划逻辑
我来帮你拆解这个问题里的DP思路,先从问题的核心简化说起:
第一步:问题转化为二进制掩码
首先,每个字符串可以用一个20位的二进制数(简称掩码mask)来表示:
- 每一位对应前20个英文字母(A-T),比如第0位对应A,第1位对应B,……第19位对应T
- 如果字符串包含某个字母,对应位设为1;否则为0。比如字符串"AB"对应的掩码是
0b11(十进制3)。
这样问题就变成了:给定一堆20位掩码,求无序对(i,j)(i≤j)的数量,使得两个掩码的按位或等于(1<<20)-1(20位全1,代表包含所有20个字母)。
为什么用动态规划?
如果直接暴力枚举所有对,时间复杂度是O(n²),当n很大(比如1e5)时肯定超时。我们需要一种更高效的方式,而状态压缩的DP(超集求和)就是解决这个问题的关键。
核心DP思路:超集求和
我们需要先统计每个掩码出现的次数,再预处理出每个掩码对应的「能和它组成有效对的字符串总数」,这里的DP就是用来快速计算这个总数的。
1. 统计掩码出现次数
先定义cnt[mask]:表示列表中,掩码恰好为mask的字符串的个数。
- 遍历所有字符串,转换为掩码后,给对应的
cnt[mask]加1。
2. 超集求和DP(计算sum数组)
我们定义sum[mask]:表示所有包含mask作为子集的掩码的字符串总数(也就是所有掩码mask'满足mask' ⊇ mask的cnt[mask']之和)。
怎么计算sum数组?用递推的方式:
- 初始化:
sum[mask] = cnt[mask],每个掩码先统计自己的数量。 - 遍历每一位(0到19):
- 对所有掩码,如果当前掩码不包含这一位,就把「包含这一位的掩码的
sum值」加到当前掩码的sum里。 - 比如处理第0位(A)时,对于掩码
0b10(只含B),我们把sum[0b11](含A和B的字符串数量)加到sum[0b10]里,这样sum[0b10]就变成了「只含B + 含A和B」的总数。
- 对所有掩码,如果当前掩码不包含这一位,就把「包含这一位的掩码的
这个递推的本质是逐步把每个掩码的超集数量合并进来,最终sum[mask]就代表了所有能覆盖mask的字符串总数。
3. 计算有效对数量
有了sum数组,我们就可以快速计算有效对:
- 首先定义
full_mask = (1 << 20) - 1(20位全1)。 - 对于每个掩码
mask,要找到能和它组成有效对的掩码mask',需要满足mask | mask' = full_mask。换句话说,mask'必须包含full_mask ^ mask(也就是mask中缺失的所有字母对应的位)。 - 所以能和
mask配对的字符串数量就是sum[full_mask ^ mask](因为sum统计了所有包含这个缺失位集合的字符串)。
4. 从有序对转无序对
先计算所有有序对的总数S:
S = 0 for mask in range(0, 1 << 20): required = full_mask ^ mask S += cnt[mask] * sum[required]
但题目要求的是无序对,这里需要调整:
same表示自己和自己能组成有效对的字符串数量,也就是cnt[full_mask](只有全1的掩码自己和自己的或才是全1)。- 有序对中,i≠j的有效对会被计算两次((i,j)和(j,i)),而i=j的有效对只被计算一次。
- 最终无序对数量为:
ans = (S + same) // 2
举个小例子验证
假设只有A、B两个字母(full_mask=0b11),列表有3个字符串:"A"(0b01)、"B"(0b10)、"AB"(0b11)。
cnt[0b01] = 1,cnt[0b10] =1,cnt[0b11]=1- 计算
sum数组后得到:sum[0b00]=3,sum[0b01]=2,sum[0b10]=2,sum[0b11]=1 - 有序对总数
S = 1*2 +1*2 +1*3 =7 same=1,最终无序对数量(7+1)/2=4,和手动枚举的有效对数量一致((0,1)、(0,2)、(1,2)、(2,2))。
这样是不是就把DP的逻辑理清楚了?核心就是用超集求和的DP避免暴力枚举,把时间复杂度从O(n²)降到O(20*2^20),完全符合题目要求。
内容的提问来源于stack exchange,提问作者Varun Kamani

