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

普通状压做法

观察到 \(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. Golang之常用方法[总结]

    1. 有一堆数字,如果除了一个数字以外,其他数字都出现了两次,那么如何找到出现一次的数字? nums := []int{1, 5, 1, 6, 5, 3, 6} i := 0 for _, v := ...

  2. PHP5.2-5.6不同版本新特性

    温故而知新, 时常复习下之前的东西 还是会有一些收获 本文目录:PHP5.2 以前:autoload, PDO 和 MySQLi, 类型约束PHP5.2:JSON 支持PHP5.3:弃用的功能,匿名函 ...

  3. Linux命令之ncdu

    简介 Ncdu - NCurses Disk Usage Ncdu 是一个带有 ncurses 接口的磁盘使用分析器. 它旨在在您没有完整图形设置可用的远程服务器上查找空间占用,但即使在常规桌面系统上 ...

  4. IntelliJ IDEA 中 ctrl + w 一键选中双引号中的字符串内容

    记录下,之前一直知道在 IntelliJ IDEA 中快速选中一个词的快捷键是 ctrl + w,可是有时我们想一键选中双引号中的字符串内容,正好这个字符串中的内容有各种特殊字符,比如",& ...

  5. openEuler创建和root一样的账号

    1. 使用以下命令在 openEuler 操作系统的 root 用户下创建管理员用户: useradd -m -G root admin -m 表示创建用户的同时创建用户的主目录, -G 表示将用户添 ...

  6. tomcat部署cas6并配置自己的ssl证书

    ​配置并安装tomcat,详见我的文章:windows安装tomcat10 安装必备的软件:(在<windows安装tomcat10>中已详细配置) apache-tomcat-10.1. ...

  7. R数据分析:非劣效性研究设计的统计处理方法,原理和实例

    在我们经常接触的统计模式中,我们是在寻求推翻原假设,证明差异,这种统计模型在传统的临床试验中,在各种统计推断中已经成为默认了.在传统的临床试验中通常会将一种新的治疗方法与标准治疗或安慰剂进行比较,从而 ...

  8. 【Web前端】【JavaScript】实现表格隔行变色

    方法1:原生JavaScript 设置CSS table td{ border:red solid 1px; } .tr1{ color:white; background: black; } .tr ...

  9. 【Vue】vue项目搭建、ES6的简单使用(大觅)

    目录 项目搭建与基本配置 项目搭建 安装淘宝NPM镜像 cnpm 安装webpack 新建项目 运行项目 运行时出现的一些问题和解决方案 框架安装 安装UI框架iView 引入UI框架iView 引入 ...

  10. 基于开源IM即时通讯框架MobileIMSDK:RainbowChat-iOS端v9.1版已发布

    关于MobileIMSDK MobileIMSDK 是一套专门为移动端开发的开源IM即时通讯框架,超轻量级.高度提炼,一套API优雅支持 UDP .TCP .WebSocket 三种协议,支持 iOS ...