状压dp小记

状压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(Wa
Wb*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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值