【学习笔记】技巧-状态压缩

【学习笔记】技巧-状态压缩

第一篇学习笔记

OI-WIKI链接

前置知识:位运算

(位运算一定记得加括号)!!!

基本知识

意义

使用二进制数的每一位(0/1)代表一个元素的状态(是/否)
譬如:\((1001)_2\) 代表第1、4个元素为True
一般可以用于表示元素是否已经选了、或者是否开关

特征

由于类型大小限制,压缩的元素数目不能多于20个左右
因此最显著的特征为:数据范围中一个维度很小,只有不到20

lowbit:返回二进制最低位的1所代表的值

ll lowbit(ll x)
{
    return x&-x;
}

遍历一个状态的所有子集

for(ll temp=state;;temp=state&(temp-1))
{
    //实体代码
    if(!temp)
        break;
}

奇技淫巧

主要题型

	1. 状压dp
	2. 容斥
	3. 前缀和

状压dp

分为 计数型(不可重)最值型(可重)

两种写法:

  • 人人为我:其他状态来更新我(正常dp比较常用,但是有可能TLE)
  • 我为人人:我更新其他状态(复杂度应该好一点)

计数型(不可重)

最大的难点在于去重。

如果是分组问题,可能出现同一种分组情况被不同的最后一组更新。(例如 \(\{1,2\} \{3,4\}\)\(\{1,2\}\)\(\{3,4\}\) 重复计算了两次)一个可行的方法是:转移时,只允许用包含大状态lowbit的那一组转移,由于lowbit只有一个,所以相同的分组方案只会转移一次。
例如:仅当(cur&state)==cur(cur&lowbit(state))!=0时,dp[state]+=dp[state^cur](cur表示当前组,state表示总状态)

最值型(可重)

不用去重,简单不少。

值得注意的是,最值问题有时候也会出错。
如果当前状态和转移来源的编号(或者其他信息)有关,就不能只写dp[state],而是要写dp[state][i],比如 P1433 吃奶酪

这是我dp没学好

容斥

状态压缩的非常规应用。有些时候非常有用

遍历 \(2^n-1\) 也就是 \((111...1)_2\) 的所有子集,如果 \(popcount(temp)\%2==1\) 那么 ans+=当前状态和 ,否则ans-=当前状态和

这个方法可以快捷地完成容斥。

前缀和

  • 人人为我:枚举每个状态的子集,加到自己上(比较慢,容易TLE)
  • 我为人人:如果自己不为0,枚举包含自己的集合,将自己加到它(比较快)

END

posted @ 2025-02-20 20:54  Luke_li  阅读(15)  评论(0)    收藏  举报