宝藏:一个对着蓝书代码调都能调两个小时的大毒瘤,但是思路还是很值得借鉴的,有普通状压和三进制状压两种做法,或者暴搜剪枝也可以(这里不介绍暴搜剪枝做法)。

普通状压做法

观察到 \(n\le 12\),首先想到状压。

但考虑到普通的状压不太行,因为 \(K\) 这个数算在代价里,会导致这个 dp 有后效性。同时也观察到最终形成的方案一定是一棵树

因此,我们尝试把 \(K\) 加入状态中。

定义 \(dp_{K,i}\) 表示这棵树扩展到第 \(K\) 层,状态为 \(i\) 时的最小花费。

那么我们在转移的时候枚举一下 \(i\) 的子集 \(j\),从 \(dp_{K-1,j}+cost\times(K-1)\) 转移就好了。

所以我们预处理的时候要把每个状态的能转移到该状态的子集列出来。

这个操作,我们可以先列出每个状态拓展所有点的边后的状态 \(expd_i\),并且记录下每个状态 \(i\) 扩展第 \(j\) 个点的最小花费 \(road_{i,j}\),目的是便于计算 \(cost\) 的值。

于是我们枚举每一个状态的子集,判断这个状态是否是该子集的 \(expd\) 的子集。如果是,则可以转移,枚举所有需要扩展的点,加上它的 \(road\) 即可。

细节

枚举某个状态的子集

枚举 \(i\) 这个状态的非零子集,可以通过如下代码实现:

for(int j=i;j!=0;j=((j-1)&i))

如果要枚举 \(0\),必须特判;如果不能枚举自身,那么把 \(j\) 初始化重设一下:int j=((i-1)&i)

例子:枚举 \(14\) 的非零子集(包括自己)。

因为每次都从某个子集减 \(1\),并且还与了自身,所以保证每次都是子集,且比以前都小,保证了不重、不漏。

位运算

注意优先级,很容易被坑,多打括号。

优先级从大到小:

  1. * 和 / 乘除
  2. + 和 - 加减
  3. >> 和 << 左移右移
  4. > 和 < 和 == 和 != 和 >= 和 <= 比较符
  5. ^ (xor) 异或
  6. | 位或

注意: ! 和 ~ 的优先级很高,不要乱用,甚至高于加减的优先级,尽量加括号使用!!!

时间复杂度分析

二项式定理:

\[(a+b)^n=C_{n}^{0} a^nb^0 +C_{n}^{1} a^{n-1}b^1 +\dots+C_{n}^{k} a^{n-k}b^k +\dots+C_{n}^{n} a^0b^n
\]

其中,组合数的系数可以看作是杨辉三角里的数,其实这个定理初二就学过。

该定理可以逆用。

那么我们用此来解决本题枚举子集部分的复杂度分析:

\[2^n+C_{n}^{1}2^{n-1}+C_{n}^{2}2^{n-2}+...+1=(2+1)^n=3^n
\]

这就是 dp 部分复杂度,预处理的复杂度为 \(O(m\times2^n)\)。

总体复杂度 \(O(n\times3^n+m\times2^n)\)。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pi;
int n,m,expd[4505];
ll d[15][15],road[4505][15],dp[15][4505],ans=0x3f3f3f3f3f3f3f3f;
vector<pi>frm[4505];
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
memset(d,0x3f,sizeof(d));
memset(road,0x3f,sizeof(road));
for(int i=1;i<=m;i++)
{
int u,v,w;
cin>>u>>v>>w;
d[u][v]=d[v][u]=min(1ll*w,d[u][v]);
}
for(int i=1;i<=n;i++)d[i][i]=0;
//初始化expand和road函数
for(int i=0;i<(1<<n);i++)
{
expd[i]=i;
for(int j=1;j<=n;j++)
{
if(((i>>(j-1))&1)==0)continue;//位运算不要偷懒,这里写 !(i>>(j-1))&1 是错的!
road[i][j]=0;
for(int k=1;k<=n;k++)
{
if(((i>>(k-1))&1)==1)continue;
if(d[j][k]>=(0x3f3f3f3f/2))continue;
expd[i]=expd[i]|(1<<(k-1));
road[i][k]=min(road[i][k],d[j][k]);
}
}
}
//初始化每个状态的子集们
for(int i=0;i<(1<<n);i++)
{
for(int j=i;j!=0;j=((j-1)&i))
{
if((i&expd[j])!=i)continue;
ll cst=0;
for(int k=1;k<=n;k++)
{
if(((i^j)>>(k-1))&1)cst+=road[j][k];
}
frm[i].push_back({j,cst});
}
}
//状压dp
memset(dp,0x3f,sizeof(dp));
for(int i=0;i<n;i++)dp[1][1<<i]=0;
for(int i=1;i<=n;i++)//层数
{
for(int j=0;j<(1<<n);j++)//当前状态
{
for(auto tmp:frm[j])
{
int st=tmp.first;
ll cst=tmp.second;
dp[i][j]=min(dp[i][j],dp[i-1][st]+cst*(i-1));// i要-1 ,不要读错题
}
}
}
for(int i=1;i<=n;i++)ans=min(ans,dp[i][(1<<n)-1]);
cout<<ans;
return 0;
}

三进制状压做法

口胡一下,有点难写。

三进制数,该位为 \(0\) 代表没有开辟,\(1\) 代表早就开辟,可以转移;\(2\) 表示刚开辟,不能转移。

这种做法依然要记录层数。除此之外还要记录 \(3\) 的次幂之类的东西,常数极大。

注意转移时只要转移最高位的 \(1\),因为其他位的 \(1\) 以后一定会循环到,避免了重复枚举。跟愤怒的小鸟那题挺像的。

复杂度 \(O(n^2\times3^n)\),代码就不放了。

Luogu P3959 宝藏 题解 [ 紫 ] [ 状压 dp ] [ 二项式定理 ]的更多相关文章

  1. 【NOIP2017】宝藏 题解(状压DP)

    题目描述 参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了 nnn 个深埋在地下的宝藏屋, 也给出了这 nnn 个宝藏屋之间可供开发的m mm 条道路和它们的长度. 小明决心亲自前往挖掘所有宝藏屋中 ...

  2. bzoj 2669 题解(状压dp+搜索+容斥原理)

    这题太难了...看了30篇题解才整明白到底咋回事... 核心思想:状压dp+搜索+容斥 首先我们分析一下,对于一个4*7的棋盘,低点的个数至多只有8个(可以数一数) 这样的话,我们可以进行一个状压,把 ...

  3. noi省选 [九省联考2018]一双木棋题解(状压dp)

    比浙江简单多了........ 题目转送:https://www.luogu.org/problemnew/show/P4363 分析: 我们注意到n和m都很小,考虑一下状压dp. 显然,棋子摆成的形 ...

  4. BZOJ 1087 题解【状压DP】

    1087: [SCOI2005]互不侵犯King Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 3112  Solved: 1816[Submit][ ...

  5. LibreOJ 6177 题解(状压DP)

    题面 传送门 分析 刚看到这道题时想的是跟最短哈密顿路类似的二进制状压DP,先用floyd处理距离 但是此题用二进制不够,应该用三进制 0,1,2分别表示未送,正在送,已送完 dp[s][i]表示当前 ...

  6. 【FZYZOJ】愚人节礼物 题解(状压DP)

    前言:麻麻我会写状压DP了! ---------------------------- 题目描述 愚人节到了!可爱的UOI小朋友要给孩子们送礼物(汗-原题不是可爱的打败图么= =..).在平面直角坐标 ...

  7. 【SCOI2005】互不侵犯 题解(状压DP)

    前言:一道状压DP的入门题(可惜我是个DP蒟蒻QAQ) ------------------ 题意简述:求在一个$n*n$的棋盘中放$k$个国王的方案数.注:当在一个格子中放入国王后,以此格为中心的九 ...

  8. P1278 单词游戏【题解】(状压dp)

    单词游戏 题目描述 Io和Ao在玩一个单词游戏. 他们轮流说出一个仅包含元音字母的单词,并且后一个单词的第一个字母必须与前一个单词的最后一个字母一致. 游戏可以从任何一个单词开始. 任何单词禁止说两遍 ...

  9. 【SCOI2008】奖励关 题解(状压DP+期望)

    题目链接 题目大意:给定$n$个宝物,每次随机抛出一个宝物,奖励分数为$p_i$.但如果选这个宝物必须选过它的前置宝物集合.共进行$K$轮问最优策略下的期望. $n\leq 15,-10^6\leq ...

  10. P3160 [CQOI2012]局部极小值 题解(状压DP+容斥)

    题目链接 P3160 [CQOI2012]局部极小值 双倍经验,双倍快乐 解题思路 存下来每个坑(极小值点)的位置,以这个序号进行状态压缩. 显然,\(4*7\)的数据范围让极小值点在8个以内(以下示 ...

随机推荐

  1. Kafka 1.1.1 源码编译

    下载源码 git clone -b 1.1.1 https://github.com/apache/kafka.git --depth=1   使用这样的方式下载主要是解决两个问题:其一,下载指定分支 ...

  2. 3D饼图

    1.实现思路 Echarts本身没有这类图形,可以使用其扩展echarts-gl进行绘制,echarts-gl曲面图可以完成这类需求 <script src="https://cdn. ...

  3. uni app 封装接api接口

    创建文件  base.js let baseURL = ''; // 是否在控制台显示接口请求日志,本地环境启用,打包环境禁用 let showHttpLog = false; // 测试环境 bas ...

  4. 前端每日一知之css常用布局单位

    脑图在线链接 本文内容依据[js每日一题]公众号精彩文章总结而来

  5. Flutter查漏补缺2

    Flutter的理念架构 Flutter架构分为三层 参考官方文档 Framework层(dart) flutter engine层(C/C++) embeder层(platform-specific ...

  6. forms组件与源码分析、modelform组件

    目录 一.forms组件 forms组件介绍 Form定义 二.forms组件渲染标签 三.forms组件展示信息 四.forms组件校验补充 五.forms组件参数补充 六.forms组件源码剖析 ...

  7. 【Javaweb】JSP标准标签库

    目录 JSTL 1.什么是JSTL 2.版本 3.标签函数库 4.优点 JSTL基本概念 标签(Tag) 标签库(Tag library) 标签库描述文件(Tag Library Descriptor ...

  8. startup

    要提取startup/后面的字符,可以使用cut命令或awk命令.以下是两种方法: 使用cut命令: bash #!/bin/bash # 给定的字符串 STR1="startup/valu ...

  9. Linux 添加开机自启动

    rc.local 方式 一.& 在 Linux 命令后加上 &  可以在后台运行 二.nohup 对 SIGHUP 信号免疫,对 SIGINT 信号不免疫,可用 shopt | gre ...

  10. KMS for Office 2021

    I. 镜像下载 官方镜像下载地址: Office 2021 专业增强版: https://officecdn.microsoft.com/pr/492350f6-3a01-4f97-b9c0-c7c6 ...