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

普通状压做法

观察到 \(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. Django之开发restful接口

    django中的开发接口有两种模式FBV和CBV,分别是基于函数视图和基于类视图,详细的可以看看菜鸟教程的Django 视图 - FBV 与 CBV,由于本文的用户管理是一个restful风格的api ...

  2. redis6.0安装与使用

    [Linux] 源码安装: 下载安装 Cd /usr/local/src/ wget https://download.redis.io/releases/redis-6.0.9.tar.gz $ t ...

  3. Golang常见问题汇总

    在开始使用golang的时候,经常会遇到各种问题,总结在此 1.unrecognized import path "golang.org/x/.. golang 在 github 上建立了一 ...

  4. 设计模式【3.3】-- CGLIB动态代理源码解读

    cglib 动态代理 cglib介绍 CGLIB 是一个开源项目,一个强大高性能高质量的代码生成库,可以在运行期拓展 Java 类,实现 Java 接口等等.底层是使用一个小而快的字节码处理框架 AS ...

  5. Linux C/C++编程中的多线程编程基本概念

    8.2.1  操作系统和多线程 要在应用程序中实现多线程,必须有操作系统的支持.Linux 32位或64位操作系统对应用程序提供了多线程的支持,所以Windows NT/2000/XP/7/8/10是 ...

  6. 使用Nginx搭建流媒体服务器

    目录 什么是流媒体服务器 Nginx如何实现流媒体服务器 为Nginx安装nginx-http-flv-module 概述 流程 操作步骤 配置流媒体服务器 使用OBS推流 使用VLC拉RTMP 使用 ...

  7. S2P主数据助力医药企业建立数据化管理平台

    随着国家信息化进程的推进,医药软件行业市场规模正在不断扩大,其应用领域也在逐步拓宽,企业面临着多样化的销售渠道和模式选择.然而,要想在这样的多变市场中占据优势地位,单纯依靠经验决策已经不足以应对挑战. ...

  8. Postman 汉化教程

    Postman 汉化教程(Postman中文版) 迷恋自留地 postman官网下载地址 https://www.postman.com/downloads/ postman汉化包 https://g ...

  9. 【C#基础】Dynamic类型和正确用法

    前言 Dynamic类型是C#4.0中引入的新类型,它允许其操作掠过编译器类型检查,而在运行时处理. 编程语言有时可以划分为静态类型化语言和动态类型化语言.C#和Java经常被认为是静态化类型的语言, ...

  10. 《前端运维》一、Linux基础--11服务

    首先服务的概念,对于linux服务器来说,其中大多数的软件都算是服务,比如nginx.mysql.Jenkins等等.下面我们就来学习一下linux中有关服务的一些内容. 一.服务简介和分类 1.运行 ...