动态规划(算法竞赛、蓝桥杯)--乱杀的状态压缩DP

文章介绍了使用状态压缩和动态规划方法解决三个不同问题:国王放置、玉米田种植和炮兵部署,通过计算合法状态和状态转移来求解最优方案。
摘要由CSDN通过智能技术生成

1、B站视频链接:E25 状态压缩DP 小国王_哔哩哔哩_bilibili

题目链接:[SCOI2005] 互不侵犯 - 洛谷

ee3a64a4243e45a99df9a013acbbed94.png

733895e5f0b849baa6fa83240d460c1c.png

28ae8c09da6d4c4d85c8ea09896383ea.png

fd98032ee26c4a02a604d68240f35272.png

e94b76f720d9470ca48c0c37139a41e3.png

#include <bits/stdc++.h> 
using namespace std;
int n,k;//棋盘行数、国王总数
int cnt;//一行合法状态的个数
int s[1<<12];//一行合法状态集,2的12次方钟状态
int num[1<<12];//每个合法状态包含的国王数
long long f[12][144][1<<12];
//f[i,j,a]//表示前i行已经放了j个国王,
//第i行的第a个状态时的方案数

int main(){
	cin>>n>>k;
	//预处理,选出合法状态和合法状态下的国王数 
	for(int i=0;i<(1<<n);i++){//枚举一行所有的状态 
		if(!(i&i>>1)){//如果不存在相邻的1 
			s[cnt++]=i;//记录一行合法的状态集 
			for(int j=0;j<n;j++){
			num[i]+=(i>>j&1);//每个合法状态包含的国王数 
		}
	}
}
	f[0][0][0]=1;//边界 
	for(int i=1;i<=n+1;i++){//枚举行 
		for(int j=0;j<=k;j++){//枚举国王数 
			for(int a=0;a<cnt;a++){//枚举第i行的合法状态 
				for(int b=0;b<cnt;b++){//枚举第i-1行的合法状态 
					int c=num[s[a]];//第i行第a个状态的国王数
					if((j>=c) //可以继续放国王 
					&&!(s[b]&s[a])//不存在同列的1 
					&&!(s[b]&(s[a]<<1))//不存在写对角的1 
					&&!(s[b]&(s[a]>>1))){ 
					f[i][j][a]+=f[i-1][j-c][b];//行间状态转移(累加求和) 
				} 
			}
		}
	}
} 
	cout<<f[n+1][k][0]<<endl;//第n+1行不放国王的方案数,省去了累加步骤 
	return 0;
}
 

cf7e8e9377214af7bb0a52c74702e141.png

0a3faed2921448a1bed55a047b118826.png

2、B站视频链接:E26 状态压缩DP 玉米田_哔哩哔哩_bilibili

题目链接:[USACO06NOV] Corn Fields G - 洛谷

4286c39255f443e68e94701640561efe.png

0630022f992544178df913ee2e3e58ec.png

435d701bffa740069e02fe26a20e9368.png

d1a30cf0b559444cbb8b1a1113b45a9f.png

#include <bits/stdc++.h> 
using namespace std;
const int P=1e8;
int n,m;
int g[14];
int cnt;
int s[1<<14];
int f[14][1<<14];
//f[i,a]表示已经种植前i行,第i行第a个状态的方案数

int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			int x;cin>>x;
			g[i]=(g[i]<<1)+x;//各行的状态值(十进制) 
		}
	}
	//预处理出合法状态
	for(int i=0;i<(1<<m);i++){//枚举状态,m列则有2的m次方种 
		if(!(i&i>>1)){//如果不存在相邻的1 
			s[cnt++]=i;
		} 
	} 
	f[0][0]=1;
	//状态计算
	for(int i=1;i<=n+1;i++){//枚举行 
		for(int a=0;a<cnt;a++){//枚举第i行的合法状态 
			for(int b=0;b<cnt;b++){//枚举第i-1行的 
				if(!(s[a]&s[b])//不能同列均为1 
				&&(s[a]&g[i])==s[a]){//种在肥沃土地上 
					f[i][a]=(f[i][a]+f[i-1][b])%P;
				}
			}
		}
	} 
	printf("%d\n",f[n+1][0]);
	return 0;
} 

3、B站视频链接:E27 状态压缩DP 炮兵部队_哔哩哔哩_bilibili

题目链接:[NOI2001] 炮兵阵地 - 洛谷

648854cd272a4bf08a00b749e371616c.png

e026068899b74a299155e39a5f586f77.png

b2caddd0513949d7906b86f7a6163061.png

9e09ad2c840d4cafa8981b401c72b919.png

 

#include <bits/stdc++.h> 
using namespace std;
const int N=110, M=1<<10;
int n,m;    //行数,列数 
int g[N];   //存储地图各行数值
int cnt;    //一行的合法状态个数
int s[M];   //一行的合法状态集 
int num[M]; //每个合法状态包含1的个数 
int f[N][M][M]; //110*1024*1024*4 = 440MB
// f[i][a][b]表示已放好前i行,
// 第i行第a个状态,第i-1行第b个状态时,能放置的最大数量 

int main(){
  cin>>n>>m;
  for(int i=1;i<=n;i++)
    for(int j=0;j<m;j++){
      char c; cin>>c; 
      if(c=='P') g[i]+=1<<(m-j-1); //地图各行数值          
    }
  
  for(int i=0; i<(1<<m); i++)   //枚举一行的所有状态 
    if(!(i&i>>1) && !(i&i>>2)){ //如果不存在11和101 
      s[cnt++]=i;               //保存一行的合法状态
      for(int j=0; j<m; j++)
      num[i]+=(i>>j&1);         //每个合法状态包含1的个数
    }
  
  for(int i=1; i<=n+2; i++) //枚举行
  for(int a=0; a<cnt; a++)  //枚举第i行合法状态
  for(int b=0; b<cnt; b++)  //枚举第i-1行合法状态
  for(int c=0; c<cnt; c++)  //枚举第i-2行合法状态
    if(!(s[a]&s[b])&&!(s[a]&s[c])&&!(s[b]&s[c])
      &&(g[i]&s[a])==s[a]&&(g[i-1]&s[b])==s[b])
        f[i][a][b]=max(f[i][a][b],f[i-1][b][c]+num[s[a]]);
      
  cout<<f[n+2][0][0]<<endl;
  return 0;
}

a4b900c5c3604962a65612bb4fb52d10.png

#include <bits/stdc++.h> 
using namespace std;
const int N=110, M=1<<10;
int n,m;    //行数,列数 
int g[N];   //存储地图各行数值
int cnt;    //一行的合法状态个数
int s[M];   //一行的合法状态集 
int num[M]; //每个合法状态包含1的个数 
int f[2][M][M]; //滚动数组 2*1024*1024*4 = 8MB
// f[i][a][b]表示已放好前i行,
// 第i行第a个状态,第i-1行第b个状态时,能放置的最大数量 

int main(){
  cin>>n>>m;
  for(int i=1;i<=n;i++)
    for(int j=0;j<m;j++){
      char c; cin>>c; 
      if(c=='P') g[i]+=1<<(m-j-1); //地图各行数值          
    }
  
  for(int i=0; i<(1<<m); i++)   //枚举一行的所有状态 
    if(!(i&i>>1) && !(i&i>>2)){ //如果不存在11和101 
      s[cnt++]=i;               //保存一行的合法状态
      for(int j=0; j<m; j++)
      num[i]+=(i>>j&1);         //每个合法状态包含1的个数
    }
  
  for(int i=1; i<=n+2; i++) //枚举行
  for(int a=0; a<cnt; a++)  //枚举第i行合法状态
  for(int b=0; b<cnt; b++)  //枚举第i-1行合法状态
  for(int c=0; c<cnt; c++)  //枚举第i-2行合法状态
    if(!(s[a]&s[b])&&!(s[a]&s[c])&&!(s[b]&s[c])
      &&(g[i]&s[a])==s[a]&&(g[i-1]&s[b])==s[b])
        f[i&1][a][b]=max(f[i&1][a][b],f[i-1&1][b][c]+num[s[a]]);
      
  cout<<f[n+2&1][0][0]<<endl;
  return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值