状压dp小记
铺砖
题意:现有nm的一块地板,需要用12的砖块去铺满,中间不能留有空隙。问这样方案有多少种
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1<<11;
int n,m,state;
ll dp[15][maxn];
//s1表示本行铺砖的状态,s2表示下一行铺砖的状态
void f(int i,int s1,int s2,int d){
if(d==m){
dp[i+1][s2] += dp[i][s1]; //铺满这一行,下一行方案数等于这一行累加
}
else{
//d个位置没有铺
if(!(s1&(1<<d))){
f(i,s1,s2|(1<<d),d+1); //竖放,并将下行对应的位置d覆盖下去
if((d+2)<=m && !(s1&(1<<(d+1)))){
f(i,s1,s2,d+2); //横放
}
}
else{
f(i,s1,s2,d+1); //第d个位置已经铺过,直接跳到d+1个位置
}
}
}
int main(){
while(~scanf("%d%d",&n,&m)){
if((n*m)&1){
puts("0");
continue;
}
if(n<m) swap(n,m); //交换n,m枚举小的指数
state = 1<<m;
memset(dp,0,sizeof(dp));
dp[1][0] = 1; //初始化
f(1,0,0,0); //预处理出第一行到第二行
for(int i=2;i<=n;++i){ //枚举每一行
for(int j=0;j<state;++j){ //枚举每个状态
if(dp[i][j]){ //dp[i][j]>0表示存在该状态
f(i,j,0,0);
}
}
}
printf("%lld\n",dp[n+1][0]); //最终答案上面n行铺满,第n+1行未铺。
}
return 0;
}
POJ 2288
题意:给出n(n<13)个小岛m条边连接,每个小岛有一个权值wi,再给出一条哈夫曼路径权值之和由三部分组成:
1)路径上经过结点的权值之和,ans1 = sigma(wi)
2)路径上相连接的两个结点u,v ans2 = sigma(WuWv);
3)路径上连通的三点a,b,c能形成一个三角形, ans3 = sigma(WaWb*c);
4)哈夫曼路径的定义为从0到n-1不重复不遗漏的经过每一个结点恰好一次。
5)以相反顺序遍历路径相同的算同一条路径。
求:
1)哈夫曼路径的最大值
2)最大值条件下的路径数目
3)没有输出0 0
思路:状压dp,n位二进制数,其中第i(0<i<n)位代表第i个小岛。
三维状压 dp[state][i][j],] 表示状态state下前一个结点为i,后一个结点为j的的路径权值最大值. 再利用一个path[state][i][j]记录路径数目,
代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1 << 13;
int dp[maxn][13][13];
ll path[maxn][13][13];
int w[13];
int g[13][13];
void init(){
memset(path,0,sizeof(path));
memset(dp,-1,sizeof(dp));
}
int main(){
int T,n,m,u,v;
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
for(int i = 0; i < n; ++i) scanf("%d",&w[i]);
for(int i = 0; i < n; ++i){
for(int j = 0; j < n; ++j){
g[i][j] = 0;
}
}
for(int i = 0; i < m; ++i){
scanf("%d%d",&u,&v);
--u,--v;
g[u][v] = 1;
g[v][u] = 1;
}
if(n == 1){
printf("%d 1\n",w[0]);
continue ;
}
init();
for(int i = 0; i < n; ++i){
for(int j = 0; j < n; ++j){
if(g[i][j]){
dp[(1<<i)|(1<<j)][i][j] = w[i] + w[j] + w[i] * w[j];
path[(1<<i)|(1<<j)][i][j] = 1;
}
}
}
int up = 1 << n;
for(int state = 0; state < up; ++state){
for(int i = 0; i < n; ++i){
if(!(state&(1<<i))) continue ;
for(int j = 0; j < n; ++j){
if(!(state&(1<<j))) continue ;
else if(!g[i][j]) continue ;
else if(dp[state][i][j] == -1) continue;
for(int k = 0; k < n; ++k){
if(!g[j][k]) continue ;
else if(state&(1<<k)) continue ;
else if(k == i) continue ;
int tmp = dp[state][i][j] + w[k] + w[j] * w[k];
if(g[i][k]){
tmp += w[i] * w[j] * w[k];
}
if(tmp > dp[state|(1<<k)][j][k]){
dp[state|(1<<k)][j][k] = tmp;
path[state|(1<<k)][j][k] = path[state][i][j];
}
else if(tmp == dp[state|(1<<k)][j][k]){
path[state|(1<<k)][j][k] += path[state][i][j];
}
}
}
}
}
int maxx = 0;
int state = (1<<n) - 1;
ll cnt = 0;
for(int i = 0; i < n; ++i){
for(int j = 0; j < n; ++j){
if(!g[i][j]) continue ;
if(maxx < dp[state][i][j]){
maxx = dp[state][i][j];
cnt = path[state][i][j];
}
else if(maxx == dp[state][i][j]){
cnt += path[state][i][j];
}
}
}
printf("%d %lld\n",maxx,cnt/2);
}
return 0;
}
POJ 3254
题意:给出N*M的牧场,0表示土地不肥沃,1表示土地肥沃,且相邻两块土地不能种植牧草,问其一共有多少种种植方案(什么都不种植也算一种。)
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1<<12;
const int mod = 100000000;
int M,N;
int state[maxn]; //可行状态数
int up; //状态数上界
int dp[15][maxn]; //
int cur[15]; //记录当前行状态
inline bool check(int x){
if(x&(x<<1)) return false; //状态x相邻位有1
return true;
}
inline bool Fit(int x,int i){ //当前状态x和第i行的整个状态cur[i]冲突
if(x&cur[i]) return false;
return true;
}
void init(){
memset(dp,0,sizeof(dp));
up = 0;
int total = 1<<N;
for(int i=0;i<total;++i){
if(check(i)) state[++up] = i;
}
}
int main(){
while(~scanf("%d%d",&M,&N)){
init();
int tmp;
for(int i=1;i<=M;++i){
cur[i] = 0;
for(int j=1;j<=N;++j){
scanf("%d",&tmp);
if(!tmp){
cur[i] += (1<<(N-j)); //以相反方式存储,即1表示不可放牧
}
}
}
for(int i=1;i<=up;++i){
if(Fit(state[i],1)){ //若第1行的状态与第i种可行状态吻合,则dp[1][i]记为1
dp[1][i] = 1;
}
}
for(int i=2;i<=M;++i){
for(int s=1;s<=up;++s){
if(!Fit(state[s],i)) continue; //判断枚举状态是否符合第i行的实际状态
for(int j=1;j<=up;++j){
if(!Fit(state[j],i-1)) continue; //当前填充状态与前一行实际状态冲突
if(state[s]&state[j]) continue; //判断是否与第i行当前填充状态冲突
dp[i][s] = (dp[i][s]+dp[i-1][j]) % mod;
}
}
}
int ans = 0;
for(int i=1;i<=up;++i){
ans = (ans + dp[M][i]) % mod;
}
printf("%d\n",ans);
}
return 0;
}
P1171 售货员的难题
题意:某乡有nn个村庄(1<n≤20),有一个售货员,他要到各个村庄去售货,各村庄之间的路程s(0<s<1000)s(0<s<1000)是已知的,且A村到B村与B村到A村的路大多不同。为了提高效率,他从商店出发到每个村庄一次,然后返回商店所在的村,假设商店所在的村庄为1,他不知道选择什么样的路线才能使所走的路程最短。请你帮他选择一条最短的路。
code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int maxn = 1<<21;
int dp[maxn][25]; //当前状态为i,到达目的结点j的路径的最短路径.
int a[25][25];
int main(){
int n;
scanf("%d",&n);
for(int i=0;i<n;++i){
for(int j=0;j<n;++j){
scanf("%d",&a[i][j]);
}
}
for(int i=0;i<=(1<<n);++i){
for(int j=0;j<=n;++j){
dp[i][j] = INF;
}
}
dp[1][0] = 0;
int state = (1<<n)-1;
for(int i=0;i<=state;++i){ //枚举状态
for(int j=0;j<n;++j){ //枚举最终到达的目的结点
if((1<<j)&i) //目的结点不能在状态i里面
continue;
for(int k=0;k<n;++k){ //枚举中间结点
if((1<<k)&i){
dp[i|(1<<j)][j] = std::min(dp[i|(1<<j)][j],dp[i][k]+a[k][j]);
}
}
}
}
int ans = 0x3f3f3f3f;
for(int i=1;i<n;++i){
ans = min(ans,dp[state][i]+a[i][0]);
}
if(n==1) ans = 0;
printf("%d\n",ans);
return 0;
}