文章目录
状压DP是一种非常暴力的做法,枚举所有可能的状态,找到要求的最佳状态,与一般dp不同,前一项与后一项有一些复杂的状态关系。
dp的参数:
- 物品个数、行数等
- 当前状态
- 上一个状态
例题
CF1042B Vitamins
思路
将ABC的有无表示成一个8个状态,枚举所有组,枚举上一个状态,得到当前状态的最优解
代码
#include<bits/stdc++.h>
#define intn long long
using namespace std;
main(void)
{
int n;
cin>>n;
string t;
int v[2000];
int w[2000];
for(int i=1;i<=n;i++)
{
cin>>v[i];
cin>>t;
for(int j=0;j<t.length();j++)
{
if(t[j]=='A')
w[i]=w[i]|1;
if(t[j]=='B')
w[i]=w[i]|1<<1;
if(t[j]=='C')
w[i]=w[i]|1<<2;
}
}
int dp[2000][10];
for(int i=0;i<=n;i++)
for(int j=0;j<=9;j++)
dp[i][j]=999999;
for(int i=0;i<=n;i++)
dp[i][0]=0;
int ans=999999;
for(int i=1;i<=n;i++)
for(int j=0;j<=1<<3;j++)//前一个状态
{
int s=w[i]|j;//s>=j;
dp[i][s]=min(dp[i][s],dp[i-1][j]+v[i]);
dp[i][j]=min(dp[i][j],dp[i-1][j]);
}
int flag=0;
for(int i=1;i<=n;i++)
{
if(dp[i][7]<999999)
{
flag=1;
ans=min(dp[i][7],ans);
}
}
if(flag==0)
cout<<"-1";
else
cout<<ans;
}
P2704 炮兵阵地
思路
- 滚动数组只需要2组
- 枚举上边两列的状态
代码
#include<iostream>
using namespace std;
int n,m,ans,dp[(1<<10)][(1<<10)][13]/*滚动数组*/,a[115],Sum[(1<<10)];
char x;
int getsum(int S) //当前状态 S 里面包含几个 1
{
int tot=0;
while(S) {if(S&1) tot++; S>>=1;}
return tot;
}
int main()
{
cin>>n>>m;
for(int i=2;i<n+2;i++)
for(int j=2;j<m+2;j++)
cin>>x,a[i]<<=1,a[i]+=(x=='H'?1:0); //转成二进制数
for(int i=0;i<(1<<m);i++)
Sum[i]=getsum(i); //初始化 Sum 数组
for(int i=0+2;i<n+2;i++)
for(int L=0;L<(1<<m);L++)
{
if(L&a[i-1] || (L&(L<<1)) || (L&(L<<2))) continue; //特判
for(int S=0;S<(1<<m);S++)
{
if(S&a[i] || L&S || (S&(S<<1)) || (S&(S<<2))) continue; //还是特判
for(int FL=0;FL<(1<<m);FL++)
{
if(FL&L || FL&S || FL&a[i-2] || (FL&(FL<<1)) || (FL&(FL<<2))) continue; //仍然是特判
dp[L][S][(i-2)%3+2]=max(dp[L][S][(i-2)%3+2],dp[FL][L][(i-1-2)%3+2]+Sum[S]); //滚动数组的实现方法
}
}
}
for(int L=0;L<(1<<m);L++)
for(int S=0;S<(1<<m);S++)
ans=max(ans,dp[L][S][(n-1)%3+2]); //结束状态可以是最后一行的任何状态
cout<<ans;
return 0;
}
1038: 裁玻璃
思路
从第二行开始,枚举上一行状态和本行状态。
代码
#include<bits/stdc++.h>
#define intn long long
using namespace std;
int dp[1100][1100],a[1100],s[1100];
int getsum(int s)
{
int res=0;
while(s)
{
if(s&1)
res++;
s>>=1;
}
return res;
}
int judge1(int s1,int sd)
{
if((s1&sd)||(s1<<1)&sd)
return 1;
else
return 0;
}
int judge2(int s1,int s2)
{
if((s1&s2)||((s1<<1)&s2)||(s1&(s2<<1)))
return 1;
else
return 0;
}
main(void)
{
int t;
int n,m,x;
cin>>t;
for(int z=1;z<=t;z++)
{
cin>>n>>m;
memset(dp, 0, sizeof(dp));
memset(a, 0, sizeof(a));
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>x;
a[i]<<=1;
a[i]+=!x;
}
}
for(int i=0;i<(1<<m-1);i++)
{
s[i]=getsum(i);
}
for(int i=2;i<=n;i++)
{
for(int p=0;p<(1<<m-1);p++)
{
if(judge1(p,a[i-1])||(p&(p<<1)))continue;
for(int q=0;q<(1<<m-1);q++)
{
if(q&(q<<1)||judge1(q,a[i])||judge1(q,a[i-1])||judge2(p,q))continue;
dp[i][q]=max(dp[i][q],dp[i-1][p]+s[q]);
}
}
}
int ans=0;
for(int i=0;i<(1<<m-1);i++)
{
ans=max(ans,dp[n][i]);
}
printf("%d\n",ans);
}
}
本文深入探讨了状压动态规划的基本概念与实践应用,通过三个经典例题——CF1042B Vitamins、P2704 炮兵阵地及1038 裁玻璃,详细解析了如何利用状压DP解决复杂状态问题,提供了完整的代码示例,适合初学者快速掌握状压DP技巧。
1077

被折叠的 条评论
为什么被折叠?



