基本的状压DP
状压DP
给出一个 \(n\timesm\) 要求棋子不相邻,求方案数
\(n < 100\)
\(m < 8\)
\(f[i][s] = \sum{f[i-1][s_2]||s\&s_2 = 0}\)(为零表示不相邻)
\(s\) 表示当前行中棋子的情况
解析:我们会发现一个问题,上述式子确实可以保证上下行不相邻,但是左右呢,对于我这个刚认识状压的菜鸡来说,很懵逼,答:状态压缩的本质:存在一个维度是在进制级别表达多组状态,这个维度的枚举时值得我们思考
状压维度的枚举十分重要,而在这题中,对于同一行不相邻的情况我们可以单独枚举出来,没有代码,但可以口胡
枚举一行中所有可能,做个预处理,用 \(cnt\) 记录即可,在转移方程时就可以按照 \(cnt\) 来枚举,且每个状态都是当前行的合法状态,对于航与航之间的处理,就出现了上述的转移方程式,
for (int i = 1; i <= (m << 1); i++)
if(legitimate) tmp[++cnt] = a[i];//当前行合法状态
for (int i = 1; i <= n; i++)
for (int s = 1; j <= cnt; j++)
{
....//一些细节
for (int s2 = 1; s2 <= cnt; s2++)
{
if(a[s] & a[s2] == 0)//是否冲突
f[i][s] += f[i - 1][s2];//合法状态
}
}
BZOJ 1087 互不侵犯
\(d[i][j][s] = \sum{dp[i][j - cnt[s]][s_2]}\)
cnt[s] 表示 s 中有多少个一,\(s_2\) 表示上一行的状态
\((s<<1|s>>1|s)\&s_2 = 0\) 表示斜方向不冲突
保证这一行没有相邻的,预处理
预处理: 枚举该行所有情况,并记录用的国王数,是否冲突和上边方法一样
int n, k, f[11][160][160], num[160], cnt, s[162], ans;
void pre()
{
cnt = 0;
for (int i=0;i<(1<<n);i++)
{
if (i&(i<<1)) continue;
int sum = 0;
for (int j=0;j<n;j++)
if(i&(1<<j)) ++sum;
s[++cnt]=i;
num[cnt]=sum;
}
return;
}
void dp()
{
f[0][1][0]= 1;
for (int i=1;i<=n;i++)
for (int j=1;j<=cnt;j++)
for (int l=0;l<=k;l++)
{
if(l>=num[j])
{
for (int t=1;t<=cnt;t++)
{
if(!(s[j]&s[t])&&!(s[t]&(s[j]>>1))&& !(s[t]&(s[j]<<1)))
f[i][j][l]+=f[i-1][t][l-num[j]];
}
}
}
for (int i=1;i<=cnt;i++)
ans += f[n][i][k];
cout<<ans;
return;
}
main() {
scanf("%lld%lld", &n,&k);
pre();
dp();
return 0;
}
位运算
-
(s&(1<<i))
判断第 \(i\) 位是不是 \(1\) -
s=s|(1<<i)
把第 \(i\) 为设置成 \(1\) -
s=s&(~(1<<i))
把第 \(i\) 位设置成 \(0\) -
s^(1<<i)
把第 \(i\) 为的值取反 -
s=s&(s-1)
把一个数字 \(s\) 二进制下最靠右的第一个 \(1\) 去掉 -
for (s0=s;s0;s0=(s0-1)&s)
依次枚举 \(s\) 的子集for (int s=0;s<=mx;s++) for (int s0=s;s0;s0=(s0-1)&s)
我们枚举一下
当 \(s=1010\) 此时 \(s_0=1010\)
减一得 \(s_0=1001\)
与\(s\) 与得 \(s_0=1000\)
减一得 \(s_0=0111\)
与\(s\) 与得 \(s_0=0010\)
减一得 \(s_0=0001\)
与\(s\) 与得 \(s_0=0000\)
这样我们得到了集合 \(s\) 的所有子集 \(s_0\) 即\(\{11,10,01,00\}\),特别神
那么时间复杂度是什么
\(O(3^N)\)
推导:对于有着 \(k\) 个 \(1\) 的二进制数字,枚举子集需要的时间复杂度为 \(2^k\) , 拥有 \(k\) 个 \(1\) 的数字的数量用组合数学可知:\(\dbinom{n}{k}\)
那么总的时间复杂度为:\(\sum_{k=0}^nC(n,i)\times2^k\)
这里给出二项式定理
\((x+y)^n=\sum_{k=0}^n\dbinom{n}{k}\times x^{(n-k)}\times y^k\)
我们将这里的 \(x\) 默认为 \(1\), 则 \(y\) 就等于 \(2\)
那么时间复杂度就为 \((1+3)^n=O(3^n)\)
交并子集 类问题
P5911
子集类问题,大概思路就是
预处理出每种情况的限制条件
在转移时,是先枚举维度状态,在枚举维度状态的子集,将维度状态分成子集合并的形式,这样好理解了,和普通的DP断点是差不多的。
这题的转移方程
\(f[i]=min\{f[j]+T[i\ xor\ j]\}\)
条件是
\(W[i\ xor\ j] <= a\)
int a,b,t[1<<B],w[1<<B],f[1<<B], mt[1<<B], mw[1<<B];
int main() {
// freopen(".in", "r", stdin);
// freopen(".out", "w", stdout);
cin>>a>>b;
int mx=(1<<b)-1;
for (int i=1;i<=b;i++) cin>>t[i]>>w[i];
for (int i=0;i<=mx;i++)
{
for (int j=1;j<=b;j++)
if(i&(1<<(j-1))){
mt[i]=max(mt[i], t[j]);
mw[i]+=w[j];
}
}
memset (f,0x3f,sizeof(f));
f[0]=0;
for (int i=0;i<=mx;i++)
{
for (int j=i;;j=(j-1)&i)
{
if(mw[i^j]<=a) f[i]=min(f[i],f[j]+mt[i^j]);
if(!j) break;
}
}
cout<<f[mx];
return 0;
}
P3226 [HNOI2012]集合选数
const int A = 1e5 + 11;
const int N = 1e5 + 10;
const int mod = 1e9 + 1;
const int inf = 0x3f3f3f3f;
int n, vis[N], num[N], cnt[1 << 21];
ll ans = 1, f[2][1 << 21];
void sol(int start) {
int len = 0, height = 0;
for(int i = start ; i <= n ; i *= 2) {
++ height;
num[height] = 0;
for(int j = i ; j <= n ; j *= 3) {
vis[j] = 1;
++ num[height];
}
len = max(len, num[height]);
}
int p = 0;
int mxs = 1 << len;
for(int i = 0 ; i < mxs ; ++ i) f[p][i] = 0;
f[p][0] = 1;
for(int i = 1 ; i <= height ; ++ i) {
p ^= 1;
for(int i = 0 ; i < mxs ; ++ i) f[p][i] = 0;
for(int s = 0 ; s < mxs ; ++ s) {
if((s & (s << 1)) == 0 && cnt[s] <= num[i]) {
for(int t = 0 ; t < mxs ; ++ t) {
if((t & (t << 1)) == 0 && cnt[t] <= num[i - 1]) {
if((s & t) == 0) {
(f[p][s] += f[p ^ 1][t]) %= mod;
}
}
}
}
}
}
ll res = 0;
for(int s = 0 ; s < mxs ; ++ s)
(res += f[p][s]) %= mod;
(ans *= res) %= mod;
}
int main() {
for(int s = 0 ; s < (1 << 21) ; ++ s) {
for(int i = 20 ; i ; -- i) {
if((s >> (i - 1)) & 1) {
cnt[s] = i;
break;
}
}
}
scanf("%d", &n);
for(int i = 1 ; i <= n ; ++ i) {
if(!vis[i]) {
sol(i);
}
}
printf("%lld\n", (ans % mod + mod) % mod);
}
拓扑序个数问题
给出拓扑图,求不同的拓扑序的方案数
$d[s] $ 表示前s集合的点中都已经在拓扑序中的方案数,转移考虑一下下一个点选什么,下一个选的点药满足他在S中的点选完后的入读0,
木听懂
放松题
1 表示这一个竖着的骨牌的上半部分,即下半部分需要接
0 表示下面不需要接
1的话必须为0
0的话可以为1或0
预处理两个状态是否可以转移,这样可\(O(1)\) 转移
NOIP 愤怒的小鸟
- 抛物线的数量为N^2
不同 状态的转移不同,决策是枚举抛物线,
重复计算优化,看课件(没写)
强制编号最小的则就会固定两点,则枚举另一个点n次
优化了一个n
bzoj 3900(有趣)
3次方靠谱,枚举子集
不知道具体是谁的脚,进而不止到和谁交换脚
!对于一个可能合法的配置,那么他们一定保证排序 排序以后他们之间的限制小于 C ,可以不断交换鹿角,得到相邻的奇偶对,(判断是否存在合法解)
合法解判断的上限: n - 1
因为贪心的强制满足鹿的交换,那么最多 n - 1 次
希望把鹿分成组,每组内交换龙角的条件,之前是将所有的鹿角交换,这样就会快许多。
选择尽量的多的合法组使得组合成满足条件全集 S
\(dp[i]\) 表示 集合为 i 的并起来是一个合法集合最小数
Hdu3001 放松题
从任意点出发走过
两位压成四位,代替三维
LIS 问题2
给出1-n排列的其中一种最长上升子序列,求原序列可能的种树
n <= 15
旅行商问题
TSP问题
时间复杂度
20 一般是 2^n, 2^n *n
N <= 16 枚举子集
还有一个没写
总结
选出不相交集合合并起来形成全集,而且总权值最大或最小, 最大方案数
可以枚举子集来做,分成子结构
注意:枚举子集如果控制最小的选的话,那么可以减少一点的重复的计算。(愤怒的小鸟)
(。。)实际上不需要所有枚举,以内抛物线不一定经过所有的子集,因此我们枚举的是抛物线的个数。
转移 Dp基本就是这样
特点 n = 16 \(3^n\)
TSP问题,对三进制状态的初探