状态压缩dp--探究朴素写法和优化写法的本质
作者:
越过山丘_0
,
2024-04-03 19:25:44
,
所有人可见
,
阅读 18
状态压缩dp,常见的套路就是一行一行放,或者一列一列放,如果没有其他的要求的话,那么状态表示就是二维的,
但一般有其他维度的限制,这里举二维为例,
f[i,j]表示已经放好了前i-1行(列),并且第i行(列)
的状态是j,这是最朴素的写法,首先想好每一列或者每一行合法的状态,再思考相邻两列或者两行的状态是否兼容,也就是
能不能进行合法的状态转移,这一步其实优化后的状压dp也需要做这一步,但仔细一想,在枚举中有很多无意义的状态,显然,
可以采用一种预处理的思路,来对状压dp进行优化,预处理出所有合法状态,再预处理出所有的合法状态转移,
这样不仅优化了无效状态,还使状态转移可以用查表的方式,
效率得到了极大的提升,AcWing 1064 小国王,很难找到一份带朴素版的题解,
在这里贴上朴素版和优化版的代码
朴素版小国王代码,朴素版会超时,11个数据过7个
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
typedef long long LL;
const int N=20;
int n,k;
LL f[N][1<<10][110];
bool check(int x){
for(int i=1;i<n;i++){
if((x>>i&1)&&(x>>(i-1)&1))return false;
}
return true;
}
int checksum(int x){
int cnt=0;
while(x){
if(x&1)cnt++;
x=x>>1;
}
return cnt;
}
int main(){
scanf("%d%d",&n,&k);
f[0][0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<=1<<n;j++){
for(int q=0;q<=k;q++){
if(check(j)){
int t=checksum(j);
if(q>=t){
for(int a=0;a<1<<n;a++){
if(check(a)&&(a&j)==0&&check(a|j))f[i][j][q]+=f[i-1][a][q-t];
}
}
}
}
}
}
LL ans=0;
for(int i=0;i<1<<n;i++)ans+=f[n][i][k];
cout<<ans;
return 0;
}
再贴一个预处理优化版的,解题思路都在注释中
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
typedef long long LL;
const int N=20;
int n,k;
LL f[N][1<<10][110];
vector<int>sum;
vector<int>head[1<<10];
bool check(int x){
for(int i=1;i<n;i++){
if((x>>i&1)&&(x>>(i-1)&1))return false;
}
return true;
}
int checksum(int x){
int cnt=0;
while(x){
if(x&1)cnt++;
x=x>>1;
}
return cnt;
}
int main(){
scanf("%d%d",&n,&k);
for(int i=0;i<1<<n;i++){
if(check(i))sum.push_back(i);
}
for(int i=0;i<sum.size();i++){
for(int j=0;j<sum.size();j++){
int a=sum[i],b=sum[j];
if(((a&b)==0)&&check(a|b))head[i].push_back(j);
}
}
f[0][0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<sum.size();j++){
for(int q=0;q<=k;q++){
int t=checksum(sum[j]);
if(q>=t){
for(int a=0;a<head[j].size();a++){
f[i][j][q]+=f[i-1][head[j][a]][q-t];
}
}
}
}
}
LL ans=0;
for(int i=0;i<sum.size();i++)ans+=f[n][i][k];
cout<<ans;
return 0;
}