暑假集训随笔3 dp进阶2

状压dp

本身没啥可说的,这玩意主打一个技巧多。

技巧1

下面是一个用于枚举某个二进制数所表示集合的子集的二进制形式的代码。

//S为二进制数 
for(int x=S;x;x=S&(x-1))
cout<<x<<" ";

技巧2

用一切方式避免直接进行严格O(n2)的枚举,可以尝试用一些方式避开,如维护各个状态所能到达的状态集合,从而在按位枚举转移方式之前确定转移一定成功。这么讲有些抽象,可以参看例题https://www.luogu.com.cn/problem/P3959 的题解代码,其中lead数组保证了在枚举当前状态的子集时所有被用于尝试转移的状态都是有用的

#include<bits/stdc++.h>
#include<iostream>
#include<vector>
#include<algorithm>
#include<set>
#include<utility>
#include<string.h>
#include<queue>
#include<stack>
using namespace std;
#define int  long long
#define dnt double
#define rep(i,j,k) for(int i=(j);i<=(k);++i)
#define dow(i,j,k) for(int i=(j);i>=(k);--i)
#define pr pair
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
//int gp() {int x;while((x=rand())<=0)srand(time(0));return x%1000;}
inline int read(int &x) {
	x=0;int ff=1;char ch=getchar();
	while (ch<'0'||ch>'9') {
		if (ch=='-') ff=-1;ch=getchar();
	}
	while (ch>='0'&&ch<='9') {
		x=x*10+ch-48;ch=getchar();
	}
	return x*ff;
}
void write(int x) {
  if (x > 9)write(x / 10);
  putchar((x % 10) + '0');
}
inline int max(int a,int b) {return a>b?a:b;}
inline int min(int a,int b) {return a>b?b:a;}
inline int exgcd(int a,int b,int&x,int&y) {
	if(b==0) {x=1,y=0 ;return a ;} 
	else {int r=exgcd(b,a%b,x,y);int t=x ;x=y ;y=t-a/b*y ;return r ;}
}
const int N=1000005;
int fpow(int x,int y) { 
	int ans=1;int base=x;
	while(y) {
		if(y&1)ans=ans*base;
		y/=2;base=base*base;
	}
	return ans;
}
int mod=1e9+7;
const int maxn=500001;
int leads[maxn];
int f[600010][15];
int graph[15][15];
int n,m,k;
signed main() {
	read(n),read(m);
	rep(i,1,n){
		rep(j,1,n){
			graph[i][j]=0x3f3f3f3f;
			if(i==j)
			graph[i][j]=0;
		}
	}
	rep(i,1,m){
		int x,y,z;
		read(x),read(y),read(z);
		graph[x][y]=min(graph[x][y],z);
		graph[y][x]=graph[x][y];
	}
	memset(f,0x3f,sizeof(f));
//	cout<<f[1][1]<<" ";
	rep(i,0,n-1){
		f[1<<i][1]=0;
	}
	if(n==1){
		cout<<0;
		return 0;
	}
	for( int i=1;i<=(1<<n)-1;++i) {
		for( int j=0;j<n;++j) if(((1<<j)|i) == i) {
			for( int k=0;k<n;++k) if(graph[j+1][k+1]!=0x3f3f3f3f) {
				leads[i]|=(1<<k);
			}
		}
	}
	int ans=0x3f3f3f3f;
	rep(j,2,n){
		rep(i,1,(1<<n)-1){
			for(int x=(i-1)&i;x;x=i&(x-1)){
				if((leads[x]|i)!=leads[x])
				continue;
				int anss=0;
				int bu=x^i;
				rep(i1,0,n-1){
							if((1<<i1)>bu)
							break;
					if((1<<i1)&bu){
					int minn=0x3f3f3f3f;
						rep(i2,0,n-1){
							if((1<<i2)>x)
							break;
							if((1<<i2)&x){
								minn=min(minn,graph[i1+1][i2+1]);
							}
						}
					//	cout<<i<<" "<<x<<" "<<minn<<" "<<j<<endl;
					anss+=minn*(j-1);
					}
				}
				f[i][j]=min(f[i][j],f[x][j-1]+anss);
			}				
			if(i==(1<<n)-1)
			ans=min(ans,f[i][j]);
		}
	}
	cout<<ans; 
	return 0;
}

技巧3

上面的代码还用到了一个不能称之为技巧的技巧,就是变量bu的定义。其本质就是原集合和被枚举子集的补集,但这一优化方式同样值得注意。总的来说状压dp的精髓还是在于用尽量多的位运算和剪枝方式来加速计算,毕竟究其本质其更像是记忆化搜索而非真正意义上的优化dp方法

决策单调性

posted @   wxk123  阅读(3)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端
点击右上角即可分享
微信分享提示