首页 > 其他分享 >Luogu P3959 宝藏 题解 [ 紫 ] [ 状压 dp ] [ 二项式定理 ]

Luogu P3959 宝藏 题解 [ 紫 ] [ 状压 dp ] [ 二项式定理 ]

时间:2024-08-02 15:17:37浏览次数:13  
标签:int 题解 状压 枚举 子集 road Luogu dp

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

普通状压做法

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

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

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

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

那么我们在转移的时候枚举一下 \(i\) 的子集 \(j\) ,从 \(dp[K-1][j]+cost*(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)&1)

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

image

因为每次都从某个子集减 \(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 +...+C_{n}^{k} a^{n-k}b^k +...+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 部分复杂度,预处理的复杂度为 \(m*2^n\) 。

总体复杂度 \(O(n*3^n+m*2^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*3^n)\) ,代码就不放了。

标签:int,题解,状压,枚举,子集,road,Luogu,dp
From: https://www.cnblogs.com/zhr0102/p/18337867

相关文章

  • BZOJ2839/LG10596 集合计数 题解(二项式反演+扩展欧拉定理)
    题目大意:一个有\(N\)个元素的集合有\(2^N\)个不同子集(包含空集),现在要在这\(2^N\)个集合中取出若干集合(至少一个),使得它们的交集的元素个数为\(K\),求取法的方案数,答案模\(10^9+7\)。为表述方便,不妨设这\(i\)个元素分别为\(1\simn\)。前置知识:二项式反演。考虑设\(g(......
  • 题解:CF1537E2 Erase and Extend (Hard Version)
    CF1537E2EraseandExtend题解分析通过观察题目,可以证明结果一定是由多次前缀复制得来的。题目要求你进行删和复制的操作,与其交替着操作,不如直接先删到最优的前缀再进行复制。现在就是要找最优的前缀。从头一位一位往后遍历。用\(l\)来存储目前最优前缀的长度,第\(i\)位......
  • 题解:CF1301B Motarack's Birthday
    CF1301DTimetoRun题解思维题。分析把一个格子视作一个点,每个点的度数都是偶数,所以这是一张欧拉图。而需要走遍整个方格图,可以证明只要\(k\)不超过\(4nm-2n-2m\)就一定有解。很明显存在很多种方案,这里我用的方案是:从左上角出发,向右走\(m-1\)步到头,再向左走\(m-1\)......
  • 题解:CF634A Island Puzzle
    CF634AIslandPuzzle题解分析由于我们仅能移动\(0\),所以其它数字的相对顺序较原来应该是不变的,所以我们从环中删除\(0\)再判断相对位置即可。还有需要注意的是本题是一个环,找到末尾需要用取模操作回到开头继续比较。示例代码#include<bits/stdc++.h>usingnamespacest......
  • 题解:CF718A Efim and Strange Grade
    CF718AEfimandStrangeGrade题解算法贪心+模拟思路分析显然,要最优每一次进位就只能五入不能四舍。而且当我们五入时,要取位数最高的。比如说\(1.3535\),我们有两种进位方式,一种是进位成\(1.4\),一种是进位成\(1.354\),显然前者更优。那这道题给的次数有啥用呢?考虑一种“......
  • 题解:CF1301D Time to Run
    CF1301DTimetoRun题解思维题。分析把一个格子视作一个点,每个点的度数都是偶数,所以这是一张欧拉图。而需要走遍整个方格图,可以证明只要\(k\)不超过\(4nm-2n-2m\)就一定有解。很明显存在很多种方案,这里我用的方案是:从左上角出发,向右走\(m-1\)步到头,再向左走\(m-1\)......
  • 题解:CF771A Bear and Friendship Condition
    CF771ABearandFriendshipCondition题解算法并查集,图的基本性质分析题目意思是,一旦有一些点联通,那么这些点必须两两直接相连。换句话讲,就是图中的每个联通块都是完全图。所谓完全图,就是图中的每个点都两两相连,假设一个完全图有\(n\)个点,那么我们可以通过乘法原理算出这......
  • 题解:CF507C Guess Your Way Out!
    CF507CGuessYourWayOut!题解算法模拟思路按照左右左右的方式先往下找,每次找到一个子节点时就看此节点为根的子树是否包含目标节点,如果包含就继续往下走,不包含说明目标叶子节点在另一边的子树上,那么肯定是先需要把这边的子树遍历完才能换到另一边,所以答案直接加上这个子树......
  • HT-018 Div3 构造 题解 [ 黄 ] [ 数学 ] [ 结论 ]
    构造:结论题,gcy数竞大佬tql%%%orz。结论先放结论:如果\(x\bmod4=2\),那么\(x\)无法被表示为\(a^2-b^2\)的形式;除此之外的其他数都可以。证明对\(a^2-b^2\)因式分解,得\(x=(a+b)(a-b)\)。当\(x\bmod2=1\)时包含\(x\bmod4=1\)和\(x\bmod4=3\)的情况。......
  • Educational Codeforces Round 168 (Rated for Div. 2)A——D题解
    EducationalCodeforcesRound168(RatedforDiv.2)A——D题解A.StrongPassword题意:给一个小写字符串密码,添加一个小写字母,使得密码更加复杂。题解:有相同的相邻的字母,再其中间添加不同的字母;如果没有相同的相邻的字母,则最后添加一个字母。#include<bits/stdc++.h>......