状压 dp 空间优化技巧:
-
滚动数组
-
提前预处理出有效状态
T1
典题限时返场。
上次讲的时候的代码用到了滚动数组,这次讲第二种优化。
其实很简单,就是在 dp 前将所有状态枚举一遍,将同行冲突的判掉,合法的用 \(st_i\) 存储即可。
方法很 bf,但经试验可以发现一千多状态中仅有几十个合法的,因此效率能快得飞起。
哦对了矩阵上的状压一般初始状态都在第 \(0\) 行。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m;
int soil[105],val[1<<15];
int dp[105][205][205];
int tot,st[205];
signed main(){
cin>>n>>m;
memset(dp,0xcf,sizeof(dp));
dp[0][1][1]=0;
char c;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>c,soil[i]=(soil[i]<<1)+(c=='P');
for(int i=0;i<(1<<m);i++){
if((i&(i<<1))||(i&(i<<2))) continue;
st[++tot]=i,val[i]=__builtin_popcount(i);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=tot;j++){
if((soil[i]&st[j])!=st[j]) continue;
for(int p=1;p<=tot;p++){
if(st[j]&st[p]) continue;
for(int q=1;q<=tot;q++){
if(st[j]&st[q]) continue;
dp[i][j][p]=max(dp[i][j][p],dp[i-1][p][q]+val[st[j]]);
}
}
}
}
int ans=-1e9;
for(int i=1;i<=tot;i++)
for(int j=1;j<=tot;j++)
ans=max(ans,dp[n][i][j]);
cout<<ans;
return 0;
}
T2 / T3
这两题一模一样且都很水典,所以放到一起讲了。
其实就是 T1 多加一个维度,即前 \(i\) 行共放了 \(x\) 个国王。
然后在循环中枚举 \(x\),有转移:
\[dp_{i,j,x}=dp_{i,j,x}+dp_{i-1,k,x-c} \]其中 \(i\) 表示当前行,\(j,k\) 表示当前行与上一行的状态,\(c\) 表示当前行放了几个国王(即 \(j\) 中 \(1\) 的个数)
然后改一下判定合法的方式即可,其他的和 T1 完全一致。
这是 T3 的做法,至于 T2 也是仅需改一下判定合法的方式,然后多加一个维度(因为有上两行)并状压列即可。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=10,K=1e2+5;
int n,k;
int dp[N][1<<N][K];
signed main(){
cin>>n>>k;
dp[0][0][0]=1;
for(int i=1;i<=n;i++){
for(int kk=0;kk<=k;kk++){
for(int j=0;j<(1<<n);j++){
int c=__builtin_popcount(j);
if(j&(j<<1)||c>kk) continue;
for(int l=0;l<(1<<n);l++){
if((j&l)||((j<<1)&l)||((j>>1)&l)||((l<<1)&l)) continue;
dp[i][j][kk]+=dp[i-1][l][kk-c];
}
}
}
}
int ans=0;
for(int i=0;i<(1<<n);i++) ans+=dp[n][i][k];
cout<<ans;
return 0;
}
作业 T1
曾经用 bf + 剪枝过的,很奇特。
但是这题状压更简单,仅需在设初始状态时将每个点的初始 dp 值设为与 \((0,0)\) 的距离即可,然后就变成 这里边的 T1 了。
注意初始化 dp 数组要 memset(dp,127,sizeof(dp))
,因为 dp 是 double
类型数组。
code
#include<bits/stdc++.h>
#define x first
#define y second
using namespace std;
const int N=16;
int n,vis;
double dp[1<<N][N];
pair<double,double> a[N];
int main(){
cin>>n;
memset(dp,127,sizeof(dp));
for(int i=0;i<n;i++)
cin>>a[i].x>>a[i].y;
for(int i=0;i<n;i++)
dp[1<<i][i]=sqrt(a[i].x*a[i].x+a[i].y*a[i].y);
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]+sqrt((a[j].x-a[k].x)*(a[j].x-a[k].x)+(a[j].y-a[k].y)*(a[j].y-a[k].y)));
double ans=1e9;
for(int i=0;i<n;i++)
ans=min(ans,dp[(1<<n)-1][i]);
cout<<setprecision(2)<<fixed<<ans;
return 0;
}
作业 T2
很有趣味的题,可惜正解不是状压。
题外话:here 是此题的加强版题解,先 mark 下以后研究。
首先很容易得到一个性质:每行、每列的炮的数量 \(\le 2\)。
依据这一点,我们想到对于每一行都会有三种列:
没有炮的列、有一个炮的列、有两个炮的列。
于是我们考虑设计如下状态:
令 \(dp_{i,j,k}\) 表示第 \(i\) 行中,\(j\) 列有一个炮,\(k\) 列有两个炮。
显然,空列数 \(= m-j-k\)。
然后我们对于第 \(i\) 行,分三种情形进行讨论:
- 一个都不放
直接 \(dp_{i,j,k}=dp_{i-1,j,k}\)。
- 放一个
我们可以选择放在空列中,
此时有一个炮的列会增加一个(\(j-1 \to j\)),
于是此时有
\[dp_{i,j,k}=dp_{i,j,k}+dp_{i-1,j-1,k} \times (m-(j-1)-k) \]同样的,我们也可以选择放在有一个炮的列中,
此时有一个炮的列会减少一个(\(j+1 \to j\)),有两个炮的列会增加一个(\(k-1 \to k\))。
于是此时有
\[dp_{i,j,k}=dp_{i,j,k}+dp_{i-1,j+1,k-1} \times (j+1) \]- 放两个
我们可以选择都放在空列中,
此时有一个炮的列会增加两个(\(j-2 \to j\)),
于是此时有
\[dp_{i,j,k}=dp_{i,j,k}+dp_{i-1,j-2,k} \times C^{2}_{m-(j-2)-k} \]我们也可以选择一个放在空列,一个放在有一个炮的列中,
此时有一个炮的列不变,有两个炮的列会增加一个(\(k-1 \to k\)),
于是此时有
\[dp_{i,j,k}=dp_{i,j,k}+dp_{i-1,j,k-1} \times j \times (m-j-(k-1)) \]我们还可以选择两个都放在有一个炮的列中,
此时有一个炮的列会减少两个(\(j+2 \to j\)),有两个炮的列会增加两个(\(k-2 \to k\)),
于是此时有
\[dp_{i,j,k}=dp_{i,j,k}+dp_{i-1,j+2,k-2} \times C^{2}_{j+2} \]其中因为只涉及到 \(m=2\) 的组合数,所以可以 \(O(1)\) 求得,具体见 code。
完结撒花!~
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=131,MOD=9999973;
int n,m;
int dp[N][N][N];
int C(int x){
return x*(x-1)/2;
}
signed main(){
cin>>n>>m;
dp[0][0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
for(int k=0;k<=m-j;k++){
dp[i][j][k]=dp[i-1][j][k];
if(j>=1) dp[i][j][k]=(dp[i][j][k]+dp[i-1][j-1][k]*(m-(j-1)-k))%MOD;
if(k>=1) dp[i][j][k]=(dp[i][j][k]+dp[i-1][j+1][k-1]*(j+1))%MOD;
if(j>=2) dp[i][j][k]=(dp[i][j][k]+dp[i-1][j-2][k]*C(m-(j-2)-k))%MOD;
if(k>=1) dp[i][j][k]=(dp[i][j][k]+dp[i-1][j][k-1]*j*(m-j-(k-1)))%MOD;
if(k>=2) dp[i][j][k]=(dp[i][j][k]+dp[i-1][j+2][k-2]*C(j+2))%MOD;
}
}
}
int ans=0;
for(int i=0;i<=m;i++)
for(int j=0;j<=m;j++)
ans=(ans+dp[n][i][j])%MOD;
cout<<ans;
return 0;
}