HDU 3001 Travelling (状压dp三进制)

本文介绍了一道使用三进制压缩动态规划解决的问题,旨在寻找遍历n个城市(每个城市至少访问一次且不超过两次)的最小费用路径。文章详细解析了状态压缩的思想和实现过程,包括状态转移方程。

http://acm.hdu.edu.cn/showproblem.php?pid=3001

Problem Description
After coding so many days,Mr Acmer wants to have a good rest.So travelling is the best choice!He has decided to visit n cities(he insists on seeing all the cities!And he does not mind which city being his start station because superman can bring him to any city at first but only once.), and of course there are m roads here,following a fee as usual.But Mr Acmer gets bored so easily that he doesn’t want to visit a city more than twice!And he is so mean that he wants to minimize the total fee!He is lazy you see.So he turns to you for help.

Input
There are several test cases,the first line is two intergers n(1<=n<=10) and m,which means he needs to visit n cities and there are m roads he can choose,then m lines follow,each line will include three intergers a,b and c(1<=a,b<=n),means there is a road between a and b and the cost is of course c.Input to the End Of File.

Output
Output the minimum fee that he should pay,or -1 if he can’t find such a route.

Sample Input

2 1
1 2 100
3 2
1 2 40
2 3 50
3 3
1 2 3
1 3 4
2 3 10

Sample Output

100
90
7


题意:
n个城市,m条道路。每个城市最少访问一次,最多访问2次。问遍历完所有的城市所需的花费,如果没有这样的路则输出-1。

解题思路:
三进制压缩第一题。总结一下跟二进制的不同。
三进制压缩多了两个数组(以本题为例):

int bit[12]={0,1,3,9,27,81,243,729,2187,6561,19683,59049};
//分别是 pow(3,i)的值,方便下面更新stata。若要访问k城市,则state+=bit[k]就将state更新了
int tri[60000][11];
//表示状态为i时,且第j位的值。这个数组可能的取值只有0,1,2

然后跟其他的状压dp一样。只不过这题不能先用floyed处理,因为限定了每个城市最多访问2次。
第一层遍历状态,第二层遍历已经访问的城市,第三层更新未访问的城市。
状态转移方程为:
dp[k][state] = min(dp[k][state],dp[j][state+bit[k]]+path[j][k]).


#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <climits>
#include <cmath>
#include <stack>
using namespace std;

typedef long long LL;

int n,m,tot;
int path[15][15];
int dp[15][60000];
int bit[12]={0,1,3,9,27,81,243,729,2187,6561,19683,59049};
int tri[60000][11];

void Gettri()
{
    tot = pow(3,10);
    for(int i=0;i<tot;i++) 
    {
        int t = i;
        for(int j=1;j<=10;j++)
        {
            tri[i][j] = t % 3;
            t/=3;
            if( t==0 ) break;
        }
    }
}
bool check(int sta)
{
    for(int i=1;i<=n;i++)
    {
        if( tri[sta][i] == 0) return false;
    }
    return true;
}
void Init()
{
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            path[i][j] = INT_MAX;
}
int main()
{
    Gettri();
    while( scanf("%d%d",&n,&m)!=EOF )
    {
        Init();
        while(m--)
        {
            int u,v,w; cin>>u>>v>>w;
            path[u][v] = path[v][u] = min(path[u][v],w);
        }

        int ans = INT_MAX;
        memset(dp,-1,sizeof(dp));
        for(int i=1;i<bit[n+1];i++)
        {
            for(int j=1;j<=n;j++) 
            {
                if( tri[i][j] ) {
                     if( i == bit[j] ) 
                     {
                         dp[j][i] = 0;
                         if(check(i)) ans = min(ans,dp[j][i]);
                     }
                     for(int k = 1;k<=n;k++)
                     {
                         if( tri[i][k] !=2 && path[j][k]!=INT_MAX && dp[j][i]!=-1 )
                         {
                             int sta = i + bit[k];
                             if(dp[k][sta]==-1) dp[k][sta] = dp[j][i] + path[j][k];
                             else
                                 dp[k][sta] = min( dp[k][sta] ,dp[j][i] + path[j][k]);
                             if(check(sta)) ans = min(ans,dp[k][sta]);
                         }
                     }
                }
            }
        }
        if(ans==INT_MAX) ans = -1;
        cout<<ans<<endl;
    }
    return 0;
}
六、DP的优化技巧 6.1 预处理合法态 很多问题中,大部分态是不合法的,可以预先筛选: cpp vector valid_states; for (int state = 0; state < (1 << n); ++state) { if (check(state)) { // 检查state是否合法 valid_states.push_back(state); } } 6.2 滚动数组优化 当态只依赖前一个阶段时,可以节省空间: cpp vector<vector> dp(2, vector(size)); // 只保留当前和上一个态 int now = 0, prev = 1; for (int i = 1; i <= n; ++i) { swap(now, prev); for (auto& state : valid_states) { dp[now][state] = 0; // 清空当前态 // 态转移… } } 6.3 记忆化搜索实现 有时递归形式更直观: cpp int memo[1<<20][20]; // 记忆化数组 int dfs(int state, int u) { if (memo[state][u] != -1) return memo[state][u]; // 递归处理… return memo[state][u] = res; } 七、常见问题与调试技巧 7.1 常见错误 位运算优先级:总是加括号,如(state & (1 << i)) 数组越界:态数是2ⁿ,不是n 初始态设置错误:比如TSP中dp[1][0] = 0 边界条件处理不当:如全选态是(1<<n)-1,不是1<<n 7.2 调试建议 打印中间态:将二进制态转换为可视化的形式 cpp void printState(int state, int n) { for (int i = n-1; i >= 0; --i) cout << ((state >> i) & 1); cout << endl; } 从小规模测试用例开始(如n=3,4) 使用assert检查关键假设 八、学习路线建议 初级阶段: 练习基本位操作 解决简单问题(如LeetCode 464、526题) 中级阶段: 掌握经典模型(TSP、棋盘覆盖) 学习优化技巧(预处理、滚动数组) 高级阶段: 处理高维(如需要同时缩多个态) 结合其他算法(如BFS、双指针) 九、实战练习题目推荐 入门题: LeetCode 78. Subsets(理解态表示) LeetCode 464. Can I Win(简单DP) 中等题: LeetCode 526. Beautiful Arrangement LeetCode 691. Stickers to Spell Word 经典题: POJ 2411. Mondriaan’s Dream(棋盘覆盖) HDU 3001. Travelling三进制) 挑战题: Codeforces 8C. Looking for Order Topcoder SRM 556 Div1 1000. LeftRightDigitsGame2 记住,掌握DP的关键在于: 彻底理解二进制态表示 熟练运用位运算 通过大量练习培养直觉 希望这份超详细的教程能帮助你彻底掌握DP!如果还有任何不明白的地方,可以针对具体问题继续深入探讨。 请帮我转成markdown语法输出,谢谢
最新发布
08-13
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值