蒙德里安的理想
思路:
状态表示:
首先,可以枚举横着放小方块的情况,然后再枚举竖着放的情况。
用
d
p
[
i
]
dp[i]
dp[i]来表示前
i
−
1
i-1
i−1列排放好时排第
i
i
i列的方案数,但是这样的话似乎无法转移,因为第
i
i
i列如何排放需要受到第
i
−
1
i - 1
i−1列的影响,所以必须要表示出第
i
−
1
i - 1
i−1列的状态。
表示状态可以用01序列表示,1表示该行是否被划分(注意由于划分出来的方块是1x2的而且枚举是横着的所以如果该行可以填充,则前一列的该行必须也要是空的)而01序列可以看做是某个数的二进制,从而我们把每个列的状态映射到了整数上。
即 d p [ i ] [ j ] dp[i][j] dp[i][j]表示第 i i i列按照 j j j方式摆放的方案数。
状态转移:
对于
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]可以由所有的合法
d
p
[
i
−
1
]
[
k
]
dp[i - 1][k]
dp[i−1][k]转移而来
合法的条件是,相邻两行不能重叠,竖直方向的连续空格必须是偶数倍
由于竖着的分割方式只有一种,所以总方案数等于横着分割的方案数
代码
#include<bits/stdc++.h>
#define ll long long
#define eps 1e-8
#define INF 0x3f3f3f3f
#define pb push_back
#define endl '\n'
#define IO ios::sync_with_stdio(false)
using namespace std;
const ll N = 12,M = 1 << 12;
const ll mod = 1e9 + 7;
ll dp[N][M],n,m;
bool st[M];
vector<int> state[M];
int main(){
IO;
while(cin >> n >> m && n * m){
for(int i = 0; i < (1 << n); i++){//计算列方向是否合法若第i中状态合法则st[i] = true;
int cnt = 0,f = 1;
for(int j = 0; j < n; j++)
if((i >> j) & 1){
if(cnt & 1){
f = 0;
break;
}
cnt = 0;
}else cnt++;
if(cnt & 1)st[i] = false;
else st[i] = true;
}
for(int i = 0; i < (1 << n); i++){//state[i]里表示的是能转移到i的状态
state[i].clear();
for(int j = 0; j < (1 << n);j++)
if((i & j) == 0 && st[i | j])
state[i].pb(j);
}
memset(dp,0,sizeof dp);
dp[0][0] = 1;
for(int i = 1; i <= m; i++)
for(int j = 0; j < (1 << n); j++){
for(auto x : state[j])
dp[i][j] += dp[i - 1][x];
}
cout << dp[m][0] << endl;
}
return 0;
}
最短Hamilton路径
思路:
状态表示:
考虑 d p [ i ] [ j ] dp[i][j] dp[i][j]表示到达第 i i i个点时走过的点的状态为 j j j所需要花费的最小路程
状态转移:
对于 d p [ i ] [ j ] dp[i][j] dp[i][j]可以考虑它能往哪里转移,也就是它下一步可以走到哪里,对于 d p [ i ] [ j ] dp[i][j] dp[i][j]若下一步能走k则有$dp[k][j + (1 << k)] = min(dp[i][j] + dis[i][k], dp[i][j + (1 << k)])
代码:
#include<bits/stdc++.h>
#define ll long long
#define eps 1e-8
#define INF 0x3f3f3f3f
#define pb push_back
#define endl '\n'
#define IO ios::sync_with_stdio(false)
using namespace std;
const ll N = 21,M = 1 << N;
const ll mod = 1e9 + 7;
int n,G[N][N],bit[N];
int dp[N][M];
int getBit(int x){//获取当前状态的每个位置上是否走过
int cnt = 0;
for(int i = 0; i < n; i++){
bit[i] = x % 2;
if(bit[i])cnt++;
x /= 2;
}
return cnt;
}
int main(){
int ans = INF;
scanf("%d",&n);
memset(dp,0x3f,sizeof dp);
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
scanf("%d",&G[i][j]);
for(int i = 1; i < (1 << n); i++){
int num = getBit(i);
for(int j = 0; j < n; j++){
if(num == 1 && bit[0] == 0)break;//如果起点不是0则不合法
if((bit[j])){
if(num == 1 && bit[0])dp[j][i] = 0;
if(dp[j][i] == INF)continue;//说明此时j点没有被转移到,所以不处理
}
for(int k = 1; k < n; k++){//由j转移到k
if(j != k && !bit[k]){
int idx = i + (1 << k);
dp[k][idx] = min(dp[k][idx],dp[j][i] + G[j][k]);
if(k == n - 1 && num == n - 1)ans = min(ans,dp[k][idx]);//只有所有的点走完了,而且终点是n - 1时才更新答案
}
}
}
}
printf("%d\n",ans);
return 0;
}