首页 > 其他分享 >Living-Dream 系列笔记 第54期

Living-Dream 系列笔记 第54期

时间:2024-04-20 19:11:40浏览次数:26  
标签:Living 方案 int 54 sum 二进制 Dream operatorname dp

状压 dp:

  • 是对 dp 状态表示的优化。

  • 若有多个维度,每个维度仅有 \(0/1\),则将状态转为一个二进制数,并以十进制数表示。

位操作(全 文 背 诵):

  • 任意二进制数位 \(\operatorname{and} \ 1\) 得本身。

  • 任意二进制数位 \(\operatorname{xor} \ 1\) 得本身。

  • 任意二进制数位 \(\operatorname{and} \ 0\) 赋值 \(0\)。

  • 任意二进制数位 \(\operatorname{or} \ 1\) 赋值 \(1\)。

  • (n>>k)&1 取二进制下 \(n\) 从右往左的第 \(k\) 位。

  • n&((1<<k)-1) 取二进制下 \(n\) 的右 \(k\) 位。

  • (1<<k)-1 得到 \(k\) 个 \(1\)。

  • n^(1<<k) 将二进制下 \(n\) 第 \(k\) 位取反。

  • n|(1<<k) 将二进制下 \(n\) 第 \(k\) 位赋值 \(1\)。

  • n&(~(1<<k)) 将二进制下 \(n\) 第 \(k\) 位赋值 \(0\)。

优先级:

  • 四则运算 \(>\) 位移运算符 \(>\) 位运算符 \(>\) 逻辑运算符

T1

本题的实质即为求所有哈密尔顿路径中边权和最小的那条路径的边权和。

求哈密尔顿路径是个 NP-Hard 问题,但本题可用状压 dp 求解。

具体地,我们发现在表示状态时,每个点都和其他的所有点有关。

因此我们直接用 \(dp_{0/1,0/1,...,0/1,i}\) 表示当前走到了点 \(i\),且其余点的状态为 \(0/1,0/1,...,0/1\),其中 \(0/1\) 表示 走过/没走过。

这个状态最多可达 16 维,实现起来很不好写。

于是我们考虑将前面的 \(0/1,0/1,...,0/1\) 压缩成一个二进制数来表示,

即令 \(dp_{i,j}\) 表示当前走到了点 \(j\),且其余点的状态为二进制下的 \(j\)。

因为初始站在点 \(0\)(状压的题尽量用 \(0\) 下标,省一半空间),

所以有初始状态:\(dp_{1,0}=0\),其余为 \(\infty\)。

然后显然答案为 \(dp_{2^n-1,n-1}\),其中 \(2^n-1\) 等价于 (1<<n)-1,表示二进制下 \(n\) 个 \(1\)。

接着易得转移

\[dp_{i,j}=\max(dp_{i,j},dp_{i \operatorname{xor} 2^j}) \]

其中 \(i \operatorname{xor} 2^j\) 等价于 n^(1<<k),表示将二进制下 \(n\) 第 \(k\) 位取反,即退回一步到点 \(j\) 的上一个点。

时间复杂度 \(O(n^22^n)\)。

code
#include<bits/stdc++.h>
using namespace std;

const int N=20,W=1e5+5;
int n;
int w[N][N];
int dp[W][N];

int main(){
	memset(dp,0x3f,sizeof(dp));
	cin>>n;
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++)
			cin>>w[i][j];
	dp[1][0]=0;
	for(int i=0;i<(1<<n);i++)
		for(int j=0;j<n;j++)
			if((i>>j)&1)
				for(int k=0;k<n;k++)
					if((i>>k)&1)
						dp[i][j]=min(dp[i][j],dp[i^(1<<j)][k]+w[k][j]);
	cout<<dp[(1<<n)-1][n-1];
	return 0;
}

T2

本题的实质即为求所有哈密尔顿回路中边权和最小的那条回路的边权和。

方法与上题类似,仅需改变初始状态与加上终点到起点的边权即可,然后终点枚举取 \(\max\) 即可。

code
#include<bits/stdc++.h>
using namespace std;

const int N=20,W=1e6+1e5;
int n;
int val[N][N];
int dp[W][N];

int main(){
	memset(dp,0x3f,sizeof(dp));
	cin>>n;
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++)
			cin>>val[i][j];
	dp[2][1]=0;
	for(int i=0;i<(1<<n);i++)
		for(int j=0;j<n;j++)
			if((i>>j)&1)
				for(int k=0;k<n;k++)
					if((i>>k)&1)
						dp[i][j]=min(dp[i][j],dp[i^(1<<j)][k]+val[k][j]);
	int ans=1e9;
	for(int i=0;i<n;i++)
		ans=min(ans,dp[(1<<n)-1][i]+val[i][1]);
	cout<<ans;
	return 0;
}

作业 T1

本题可用轮廓线 dp(即插头 dp)实现,但笔者目前还不会,代填坑。

考虑怎么用状压做。

观察到本题的行、列数都很小,因此怎么状压都行,这里以状压行为例。

令 \(dp_{i,j}\) 表示第 \(i\) 行种草状态为二进制下的 \(j\) 时的方案数。

显然答案为 \(\sum^{2^n-1}_{i=0} dp_{m,i} \bmod 10^8\),

并有初始状态:\(dp_{0,0}=1\)(所有地都不中也是一种方案)。

转移也十分显然

\[dp_{i,j}=(dp_{i,j}+dp_{i-1,k}) \bmod 10^8 \]

关键是如何判定某一种草方案是否合法。

  • 地的限制

    我们用 \(dirt_i\) 记录第 \(i\) 行的土地贫瘠状况,\(0/1\) 表示 肥沃/贫瘠的土地。

    对于某一种草方案 \(i\),容易发现当 \(dirt_i \operatorname{and} i=i\) 时,说明这一种草方案的草都种在了肥沃土地上。

  • 左右限制

    对于某一种草方案 \(i\),容易发现当 \(\frac{i}{2} \operatorname{and} i=0\) 时,说明这一种草方案没有左右相邻的两块草地。其中 \(\frac{i}{2}\) 等价于 i>>1

  • 上下限制

    对于某一种草方案 \(i\) 与其上一排的种草方案 \(j\),容易发现当 \(i \operatorname{and} j=0\) 时,说明这一种草方案没有上下相邻的两块草地。

时间复杂度 \(O(2^{2n}m)\)。

code
#include<bits/stdc++.h>
using namespace std;

const int N=15,MOD=1e8;
int m,n;
int dirt[N];
int dp[N][1<<N];

int main(){
	cin>>m>>n;
	int x;
	for(int i=1;i<=m;i++)
		for(int j=1;j<=n;j++)
			cin>>x,dirt[i]=(dirt[i]<<1)+x;
	dp[0][0]=1;
	for(int i=1;i<=m;i++)
		for(int j=0;j<(1<<n);j++)
			if(((dirt[i]&j)==j)&&(((j<<1)&j)==0))
				for(int k=0;k<(1<<n);k++)
					if((j&k)==0)
						dp[i][j]=(dp[i][j]+dp[i-1][k])%MOD;
	int ans=0;
	for(int i=0;i<(1<<n);i++)
		ans=(ans+dp[m][i])%MOD;
	cout<<ans;
	return 0;
}

作业 T2

典中典。似乎是 作业 T1 的加强版。

考虑到 \(N\) 较大而 \(M\) 较小,因此我们仍然状压行。

因为这题关系到上两行,所以状态应多带一个维度,即令 \(dp_{i,j,k}\) 表示第 \(i\) 行的状态为二进制下的 \(j\),且第 \(i-1\) 行的状态为二进制下的 \(k\) 时最多能摆放的炮兵部队的数量。

因此答案即为 \(\max^{2^n-1}_{i=0} \max^{2^n-1}_{j=0} dp_{n-1,i,j}\)(以第 \(0\) 行为第一行,第 \(n-1\) 行为最后一行)。

初始状态:

  • 对于第 \(0\) 行的所有合法方案 \(i\),令 \(dp_{0,i,0}=sum_i\)。

  • 对于第 \(0,1\) 行的所有合法方案 \(i,j\),令 \(dp_{1,i,j}=sum_i+sum_j\)。

其中 \(sum_i\) 表示某一方案 \(i\) 二进制下 \(1\) 的个数,等价于 __builtin_popcount(i)

转移也就自然可以得出

\[dp_{i,j,k}=\max(dp_{i,j,k},dp_{i-1,k,l}+sum_i) \]

(\(i,j,k,l\) 表示 当前行,当前行的状态与上两行的状态)

判定合法的方法同作业 T1,此处不再赘述。

code
#include<bits/stdc++.h>
using namespace std;

const int N=12,M=1e2+5;
int n,m;
int land[M];
int dp[3][1<<N][1<<N];

int val(int x){
	int sum=0;
	for(;x;x>>=1) if(x&1) sum++;
	return sum;
}

int main(){
	cin>>n>>m;
	char x;
	for(int i=0;i<n;i++)
		for(int j=0;j<m;j++)
			cin>>x,land[i]=(land[i]<<1)+(x=='P'?1:0);
	for(int i=0;i<(1<<m);i++)
		if(!(((i&land[0])!=i)||(i&(i<<1))||(i&(i<<2))))
			dp[0][i][0]=val(i);
	for(int i=0;i<(1<<m);i++)
		for(int j=0;j<(1<<m);j++)
			if(!(((i&land[1])!=i)||(i&(i<<1))||(i&(i<<2))||((j&land[0])!=j)||(j&(j<<1))||(j&(j<<2))||(i&j)))
				dp[1][i][j]=val(i)+val(j);
	for(int i=2;i<n;i++){
		for(int j=0;j<(1<<m);j++){
			if((j&(j<<1))||(j&(j<<2))||((land[i]&j)!=j))
				continue;
			for(int k=0;k<(1<<m);k++){
				if((j&k)||(k&(k<<1))||(k&(k<<2))||((land[i-1]&k)!=k))
					continue;
				for(int l=0;l<(1<<m);l++){
					if((j&l)||(k&l)||(l&(l<<1))||(l&(l<<2))||((land[i-2]&l)!=l))
						continue;
	 				dp[i%3][j][k]=max(dp[i%3][j][k],dp[(i-1)%3][k][l]+val(j));
				}
			}
		}
	}
	int ans=-1e9;
	for(int i=0;i<(1<<m);i++)
		for(int j=0;j<(1<<m);j++)
			ans=max(ans,dp[(n-1)%3][i][j]);
	cout<<ans;
	return 0;
}

标签:Living,方案,int,54,sum,二进制,Dream,operatorname,dp
From: https://www.cnblogs.com/XOF-0-0/p/18147937

相关文章

  • Codeforces 954I Yet Another String Matching Problem
    考虑到这个答案怎么算。能发现相当于是对应的字符间相连边,那么一个连通块中的字符就要变成同一个字符。于是一个连通块的代价就是\(sz-1\)。所以令有\(x\)个连通块,最后的代价就是\(|\Sigma|-x\)。考虑到因为\(|\Sigma|=6\),而\(B_6=203\)(贝尔数,\(B_n\)意义为大......
  • P3354 [IOI2005] Riv 河流
    P3354[IOI2005]Riv河流树形dp我们很容易套路地用\(f_{u,i}\)表示在\(u\)子树中,\(u\)节点放了\(i\)个伐木场的最小花费。但是这样无法转移,原因是无法表示路径长度,也无法知道运送数量。所以我们现在考虑增加状态,能够表示出距离。只考虑\(u\)节点的花费,其木头要运送......
  • lc54 螺旋矩阵
      publicList<Integer>spiralOrder(int[][]matrix){intl=0;intr=matrix[0].length-1;intu=0;intd=matrix.length-1;List<Integer>list=newArrayList<>();while(l<=r&am......
  • CMU15418(1)- 背景知识
    本系列是Prof KayvonFatahalian2017年夏季学期在清华开的一门课程,对应的CMU课程是15-418,可以在bilibili找到原始视频。这门课我是2020年学习的,现在把一部分当时的学习笔记上传博客保存。不同层次上的并行计算指令级并行(ILP,e.g.superscalar):由CPU硬件设计实现,在一个时钟......
  • CF154C Double Profiles 题解
    CF154CDoubleProfiles题解思路解析题目说的很明白,求有多少个无序点对\((i,j)\),使得与\(i\)直接相连的点集与直接与\(j\)相连的点集完全相等。我们想到如果直接判断每个\(i,j\)肯定会超时,所以我们想把每一个与任意一点直接相连的点集进行压缩,可以想到使用字符串哈希的......
  • 代码随想录算法训练营第8天 | 字符串 344.反转字符串 541. 反转字符串II 卡码网:54.
    leetcode344.反转字符串题目344.反转字符串编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组s的形式给出。不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用O(1)的额外空间解决这一问题。解题思路实现代码......
  • 代码随想录算法训练营第7天 | 哈希表 454.四数相加II 383. 赎金信 15. 三数之和 18.
    leetcode454.四数相加II题目454.四数相加II解题思路实现代码leetcode383.赎金信题目383.赎金信解题思路实现代码leetcode15.三数之和题目15.三数之和解题思路实现代码leetcode454.四数相加II题目18.四数之和解题思路实现代码......
  • Living-Dream 系列笔记 第53期
    妙妙题大合集。T1令\(dp_{i,j}\)表示分离出以\(i\)为根的恰含\(j\)节点的树所需的最小删边数。有初始状态\(dp_{i,1}=\)其子节点个数,其余为\(\infty\)。对于答案,我们考虑到对于每个节点\(i\),除了其子树内的删边数之外,它的父节点与它的连边也应删去(注意根节点\(root......
  • [CF1954] Educational Codeforces Round 164 (Rated for Div. 2) 题解
    [CF1954]EducationalCodeforcesRound164(RatedforDiv.2)题解A.PaintingtheRibbon最优策略是染\(\lceil\dfrac{n}{m}\rceil\)个颜色,然后Bob会把剩下的都染成这个颜色voidwork(){intn,m,k,c;cin>>n>>m>>k;c=(n+m-1)/m;......
  • Educational Codeforces Round 154 (Rated for Div. 2)
    B和C写的太慢了。吃了不该吃的罚时,C还莫名其妙的T了一发,另一发也是不应该T的。B连想了两个假做法,然后甚至都实现了,然后过不了样例,再基于这两个才想到了真做法。当时的思路已经有些模糊了,但是确实是写的太慢了,而且\(O(n^2)\)的限制给的也很宽裕,但是我居然还傻乎乎的去先\(O(n^2)......