day 33 状态压缩dp

二维状态压缩dp

对于解决哈密顿回路问题的状态压缩dp只能计算固定起点到其他点的总方案数最小路径

回路计数

小蓝现在在第一栋教学楼,他想要访问每栋教学楼正好一次,最终回到第一栋教学楼

(即走一条哈密尔顿回路)

可看做:从第一栋开始到 遍历完其他的所有方案数

状态压缩从第0位开始,因此在初始化邻接矩阵时要转换一下

#include <bits/stdc++.h>
using namespace std; 

long long a[22][22], dp[1 << 22][22], ans;
//dp[i][j]:i种状态,走到教学楼j的方案数 (数组稍微开大一点)
int main()
{
    for(int i = 1; i <= 21; i++)
        for(int j = 1; j <= 21; j++)
            if(__gcd(i, j) == 1) 
                a[i - 1][j - 1] = a[j - 1][i - 1] = 1;//从第0位开始 
    dp[1][0] = dp[0][1] = 1;//2楼到1楼 
    // 共2^21-1(即全1)种状态 
    for(int i = 1; i <= (1 << 21) - 1; i++){//考察状态i 
        for(int j = 0; j < 21; j++){//走到教学楼j 
            if(! (i >> j & 1)) continue; //如果状态i不经过教学楼j
            for(int k = 0; k < 21; k++){
                if((i >> k & 1) || ! a[j][k])continue;//如果状态i已经过k或者楼jk之间无路
                //从新状态i+1<<k,到楼k的方案数 = i到k + i到j到k 
                dp[i + (1 << k)][k] += dp[i][j]; 
            } 
        }
    }
    for(int i = 0; i < 21; i++)
        ans += dp[(1 << 21) - 1][i];//全1状态,最后一次经过i楼,然后最后回到1楼(因为1与所有数互质所以一定有路) 
    cout << ans << endl; 
    return 0;
}

吃奶酪

房间里放着 n块奶酪。一只小老鼠要把它们都吃掉,问至少要跑多少距离?老鼠一开始在 (0,0) 点处。

固定起点(0, 0),遍历,最短路径

memset(a, 127, sizeof(a))

https://www.cnblogs.com/ljysy/p/12535388.html

https://blog.51cto.com/u_3044148/4005292

#include <bits/stdc++.h>
using namespace std;

double x[20], y[20], dp[20][(1 << 15) + 15];
int n;
double dis(int i,int j){
    return sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
} 
int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> x[i] >> y[i];
    memset(dp, 127, sizeof(dp));//设置各状态距离 
    for(int i = 1; i <= n; i++)
        for(int j = i + 1; j <= n; j++)//状态:在i点直接到j距离dp[i][j] 
            dp[i][j] = dp[j][i] = dis(i, j); 
    for(int i = 1; i < (1 << n); i++){//遍历所有状态 
        for(int j = 1; j <= n; j++){ 
            if(! (1 & (i >> (j - 1)))) continue;//不过第j块奶酪 
            //if(! (i & (1 << (j - 1)))) continue; //相当于上面 
            for(int k = 1; k <= n; k++){ 
                if(k == j || !(1 & (i >> (k - 1)))) continue;//与j同或该状态不过第k块 
                dp[j][i] = min(dp[j][i], dp[k][i - (1 << (j - 1))] + dis(j, k));//i-k-j 
            }     
        }
    } double ans = 1e9;
    for(int i = 1; i <= n; i++)
        ans = min(ans, dp[i][(1 << n) - 1] + dis(i, 0));//从(0,0)开始 
    printf("%.2lf\n", ans);
    return 0;
}

郊区春游

#include <bits/stdc++.h>
using namespace std; 

int n, m, r, vis[205], dis[205][205], edge[20][20], dp[205][(1 << 15) + 15]; 
int main()
{
    memset(dp, 127, sizeof(dp));
    cin >> n >> m >> r;
    for(int i = 1; i <= r; i++) cin >> vis[i];
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n; j++)
            dis[i][j] = 1e9;//从i到j最短距离初始化(无穷大:ij间没有路) 
    int a, b, c;
    for(int i = 0; i < m; i++){
        cin >> a >> b >> c;
        dis[a][b] = dis[b][a] = c;//有路
    }
    //floyd求i到j最短距离
    for(int k = 1; k <= n; k++){
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= n; j++){
                if(dis[i][j] > dis[i][k] + dis[k][j])
                    dis[i][j] = dis[i][k] + dis[k][j];
            }
        }
    } 
    //把点转化为 1 2 3 4 ... r:问题变为从固定点走,经过每个点过一次距离最短多少?
    for(int i = 1; i <= r; i++)
        for(int j = 1; j <= r; j++)
            edge[i][j] = dis[vis[i]][vis[j]];
    // tsp状态压缩dp
    for(int i = 1; i < (1 << r); i++){
        for(int j = 1; j <= r; j++){//状态为i时走到点j 
            if(!(i & (1 << (j - 1)))) continue;//状态i不过点j
            if(i == (1 << (j - 1))){ dp[j][i] = dp[i][j] = 0;continue;}//起点 
            for(int k = 1; k <= r; k++){//不过j,从k到j 
                if(dp[j][i] > dp[k][i - (1 << (j - 1))] + edge[j][k])
                    dp[j][i] = dp[k][i - (1 << (j - 1))] + edge[j][k];
            }
        } 
    }
    int ans = 1e9;
    for(int i = 1; i <= r; i++)
        ans = min(ans, dp[i][(1 << r) - 1]);
    cout << ans << endl; 
    return 0;
}

一维状态压缩dp

[蓝桥杯 2019 省 A] 糖果

总共m种糖果,状态有2^m - 1种情况

#include <bits/stdc++.h>
using namespace std; 

int n, m, k, tt, t, a[105], dp[1 << 20]; 
int main()
{
    cin >> n >> m >> k;
    memset(dp, -1, sizeof(dp)); //买不到全部则输出-1 
    for(int i = 0; i < n; i++){
        tt = 0;
        for(int j = 0; j < k; j++){
            cin >> t;
            tt = tt | (1 << (t - 1)); //二进制表示第i包糖果中有哪几种糖果 
        }
        a[i] = tt, dp[tt] = 1;//状态tt最少需1包 
    }
    for(int i = 0; i < n; i++){
        for(int j = 0; j < (1 << m); j++){
            if(dp[j] == -1) continue;//没有该组合(状态)
            else if(dp[j | a[i]] == -1) dp[j | a[i]] = dp[j] + dp[a[i]];//新状态无记录 
            else dp[j | a[i]] = min(dp[j | a[i]], dp[j] + dp[a[i]]); //有记录,记录最小的 
        }
    }
    cout << dp[(1 << m) - 1] << endl;//输出买到所有糖果的最少包数 
    return 0;
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值