hdu3001(状态压缩dp,三进制!)

Travelling

Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 5896    Accepted Submission(s): 1908


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

这题的状态压缩不是二进制了,换到了三进制!!

这是为何??

题目中明确的说了每个点最多走2次,也就是说压缩为二进制并不能直接求出结果了,因为二进制只能代表一个点是否被走过的状态,而具体走过了几次却并不能记录!!然而我们题目要求可以走两次呀!怎么办?!大牛们想到了办法,压缩为三进制!

将状态压缩为三进制之后,那么显然,我们的状态数增多了,那么这些增加的状态数代表着什么呢?

举个栗子:将46化为3进制之后是1201,那么我们就可以暴力的来表示第1个点去过1次,第2个点没去过,第3个点去过2次,第4个点也去过1次!

用上面这个例子来说明一个问题,就是我们用三进制来压缩了题目要求的所有的状态,因为每个数位可以是2了,这个2是有意义的!就是表示某个点是否去过2次!

然后dp部分是状态压缩的常规解法,主要在于理解为何要化为3进制的状态压缩。



#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<queue>
using namespace std;
typedef long long ll;
int dp[60000][11];///dp[i][j]表示i状态下以j结尾的最短步数
int three[11];///three[i]表示3的i次方是多少
int digit[60000][11];///digit[i][j]表示状态i的第j位是什么数字(0,1,2)
int tu[15][15];
int n,m;
const int INF=99999999;

void init()
{
    three[0]=1;
    for(int i=1; i<11; i++)
        three[i]=three[i-1]*3;
    for(int i=0; i<three[10]; i++)
    {
        int tem=i;
        for(int j=0; j<10; j++)
        {
            digit[i][j]=tem%3;
            tem/=3;
        }
    }
}
int main()
{
    init();
    while(~scanf("%d%d",&n,&m))
    {
        for(int i=0; i<n; i++)
            for(int j=0; j<n; j++)
                tu[i][j]=INF;
        for(int i=0; i<three[n]; i++)
            for(int j=0; j<n; j++)
                dp[i][j]=INF;
        while(m--)
        {
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            tu[a-1][b-1]=tu[b-1][a-1]=min(c,tu[a-1][b-1]);
        }
        for(int i=0; i<n; i++)
            dp[three[i]][i]=0;///dp的初始化,将起点都找出来
        int ans=INF;
        for(int j=0; j<three[n]; j++)
        {
            bool flag=1;
            for(int i=0; i<n; i++)
            {
                if(digit[j][i]==0)flag=0;///只要三进制数中存在一个0,那么就说明还有点没有遍历完,就不能当做最终答案来求
                if(dp[j][i]!=INF)
                    for(int k=0; k<n; k++)
                        if(tu[i][k]!=INF&&digit[j][k]!=2)///注意这个digit[j][k]!=2,因为如果j状态在k点已经走过两次了显然是不能继续往下走的
                            dp[j+three[k]][k]=min(dp[j][i]+tu[i][k],dp[j+three[k]][k]);
            }
            if(flag)
                for(int i=0; i<n; i++)///由于是3进制,不能方便的判断一串三进制数里面是否存在0,所以在dp过程中一边dp一边直接统计结果,也是迫于无奈T_T
                    ans=min(ans,dp[j][i]);
        }
        if(ans>=INF)
            printf("-1\n");
        else
            cout<<ans<<endl;
    }
    return 0;
}


posted @ 2016-03-24 20:25  martinue  阅读(1973)  评论(0编辑  收藏  举报