hdu3001(状压dp,三进制)

时间:2023-12-14 11:09:50

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

以下文字转载于https://www.cnblogs.com/martinue/p/5490432.html

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

这是为何??

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

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

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

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

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

#include<stdio.h>
#include<string.h>
#define Max 0x3fffffff
int dp[60000][12];//记录dp[i][j]当第i种情况时以j地点为结束地点的总路程,例:46=(1201)3;则dp[46][0](i=46,j=0)代表当1走1次,2走0次,3走2次,4走1次时最后以1为终点所走路程。
int num[12];//记录3的几次方{1,3,9。。。。59049(3的10次方)}
int mp[12][12];//记录a到b的路程
int dight[60000][12];//记录第i中种情况j号位走了多少次j=(0---->n-1)
int min(int a,int b)
{
  return (a>b)?b:a;
}
int init()//初始化所需条件
{
  int i,j;
  int a;
  num[0]=1;
  for(i=1;i<=10;i++)
  num[i]=num[i-1]*3;
  for(i=0;i<num[10];i++)
  {
    a=i;
    for(j=0;a!=0;j++)
    {
      dight[i][j]=a%3;
      a=a/3;
      //printf("%d",dight[i][j]);
    }
    //printf("\n");
  }
  return 0;
}
int main()
{
  int n,m;
  int i,j,k;
  int a,b,c;
  int flag;
  int mx;
  init();
  while(scanf("%d%d",&n,&m)!=EOF)
  {
 `    mx=Max;
    for(i=0;i<num[n];i++)//初始化第i种情况以j为终点的路程
    {
      for(j=0;j<n;j++)
      dp[i][j]=Max;
    }
    for(i=0;i<n;i++)
    {
      dp[num[i]][i]=0;//初始化起点,因为num[i][i]转换为3进制时就等于只有i号为走了一次的情况 ,而它又以i为终点,所以这就是以i号为起点的情况
      mp[i][i]=0;
      for(j=0;j<i;j++)
      mp[i][j]=mp[j][i]=Max;
    }
    for(i=0;i<m;i++)
    {
      scanf("%d%d%d",&a,&b,&c);
      mp[a-1][b-1]=mp[b-1][a-1]=min(mp[a-1][b-1],c);//我们是从0-->n-1的,记得减一
    }
    for(i=0;i<num[n];i++)
    {
      flag=1;
      for(j=0;j<n;j++)
      {
        if(dight[i][j]==0)//记录是否每个位置都到过
        flag=0;
        if(dp[i][j]!=Max)//当第i种情况时以j为终点有一段路程可以满足的这个条件时
        {
          for(k=0;k<n;k++)//因为dp[i][j]这种情况存在,所以可以更新dp[i+num[k]][k]的这种情况,i+num[k]相当于在i这种情况下在k号位置加一,然后就以k为终点。
          {
            if(dight[i][k]<=1&&j!=k&&mp[j][k]!=Max)//判断满足条件,由题意可知,k号位置经历的次数不能超过两次,所以dight[i][k]<=1,并且要有路从j到k。
            {
              int nt=i+num[k];//当第i种情况时k号位加一的情况
              dp[nt][k]=min(dp[nt][k],dp[i][j]+mp[j][k]);//更新
            }
          }
        }
      }
      if(flag==1)
      {
        for(j=0;j<n;j++)
        mx=min(mx,dp[i][j]);//更新最短路程
      }
    }
    if(mx==Max)
    printf("-1\n");
    else
    printf("%d\n",mx);
  }
return 0;
}