HDU3001
2014-02-24 17:17
309 查看
HDU3001Travelling
现在有一个具有n个顶点和m条边的无向图(每条边都有一个距离权值),小明可以从任意的顶点出发,他想走过所有的顶点而且要求走的总距离最小,并且他要求过程中走过任何一个点的次数不超过2次。
输入:包含多组实例。每个实例第一行为n(1<=n<=10)和m,接下来m行是对m条边的描述,每行包括a,b(1<=a,b<=n)和c表示节点a和b之间有一条长c的路。
输出:输出他需要走的最短距离,如果不存在这样的路,输出-1.
分析:
首先本题的图一定要是一个连通的无向图,否则肯定不存在要求的路。且只要是连通的,那么肯定存在要求的路。
如果本题没有说任意一个点走过的次数不超过2这个条件,那么本题可以用类似TSP问题的解法即可,即用d[i][S]=x,表示当前人在i点,并且走过了集合S中的所有点,所行走的最小距离为x。d[i][S]=min{ d[j][S-i]+min_dist[j][i] } j,i属于S。
但是现在要求点出现的次数<=2,所以要把点出现的次数也纳入到d的第二维下标(即S)中去。
常规我们用S的二进制形式如0101表示点出现的状态含义是:0点出现1次,1点出现0次,2点出现1次,3点出现0次。现在我们需要表示的状态出现次数可能是0,1,2.所以我们应该用S的三进制形式如1201来表示点出现的情况。
令d[i][S]=x,表示当前人在i点,并且走过了集合S(用S的三进制表示点出现次数的集合)中的所有点以及对应点出现的次数满足S集合时,所行走的最小距离为x。
状态转移方程是:d[i][S]=min{ d[j][S1]+dist[j][i] }
,S与S1的关系是S的三进制形式除了一位减一之外(从2变1或从1变0)其他所有位都与S1的三进制数对应位相同,j在S1集合中出现的次数属于[1,2],i在S1集合中出现的次数属于[0,1],dist[i][j]表示节点i与j之间的初始距离(不是最小距离,如果从i到j没有边则不能执行该状态转移)。初值为:d[i][(3^i)]=0,其他为-1.(本题顶点的标号,题目说是1到n,我们程序中用0到n-1处理。最终我们所求为:max{
d[i][S] },0<=i<=n-1。S的三进制形式中没有一个0(最多2次是通过状态转移方程来限制住的)。
注意:本题的输入边有重边。
AC代码:2250ms->2140ms(预先用枚举生成所有合法的最终状态)
[b]AC代码:预处理(预先计算了base[value][p])之后递推的时间是437MS,递归记忆化搜索的时间是625MS。下面是两段代码的合并:
现在有一个具有n个顶点和m条边的无向图(每条边都有一个距离权值),小明可以从任意的顶点出发,他想走过所有的顶点而且要求走的总距离最小,并且他要求过程中走过任何一个点的次数不超过2次。
输入:包含多组实例。每个实例第一行为n(1<=n<=10)和m,接下来m行是对m条边的描述,每行包括a,b(1<=a,b<=n)和c表示节点a和b之间有一条长c的路。
输出:输出他需要走的最短距离,如果不存在这样的路,输出-1.
分析:
首先本题的图一定要是一个连通的无向图,否则肯定不存在要求的路。且只要是连通的,那么肯定存在要求的路。
如果本题没有说任意一个点走过的次数不超过2这个条件,那么本题可以用类似TSP问题的解法即可,即用d[i][S]=x,表示当前人在i点,并且走过了集合S中的所有点,所行走的最小距离为x。d[i][S]=min{ d[j][S-i]+min_dist[j][i] } j,i属于S。
但是现在要求点出现的次数<=2,所以要把点出现的次数也纳入到d的第二维下标(即S)中去。
常规我们用S的二进制形式如0101表示点出现的状态含义是:0点出现1次,1点出现0次,2点出现1次,3点出现0次。现在我们需要表示的状态出现次数可能是0,1,2.所以我们应该用S的三进制形式如1201来表示点出现的情况。
令d[i][S]=x,表示当前人在i点,并且走过了集合S(用S的三进制表示点出现次数的集合)中的所有点以及对应点出现的次数满足S集合时,所行走的最小距离为x。
状态转移方程是:d[i][S]=min{ d[j][S1]+dist[j][i] }
,S与S1的关系是S的三进制形式除了一位减一之外(从2变1或从1变0)其他所有位都与S1的三进制数对应位相同,j在S1集合中出现的次数属于[1,2],i在S1集合中出现的次数属于[0,1],dist[i][j]表示节点i与j之间的初始距离(不是最小距离,如果从i到j没有边则不能执行该状态转移)。初值为:d[i][(3^i)]=0,其他为-1.(本题顶点的标号,题目说是1到n,我们程序中用0到n-1处理。最终我们所求为:max{
d[i][S] },0<=i<=n-1。S的三进制形式中没有一个0(最多2次是通过状态转移方程来限制住的)。
注意:本题的输入边有重边。
AC代码:2250ms->2140ms(预先用枚举生成所有合法的最终状态)
#include<algorithm> #include<cstring> #include<cstdio> #include<cmath> using namespace std; int d[12][60000];//d[i][S]=-1表还没计算过,=1e10表已经计算过但是没有路,>=0表计算过且有路 int n,m; int dist[15][15]; int t[12]; int cnt; int state[60000]; bool legal(int S)//判断S的三进制形式中是否没有一个0 { bool ok = true; for(int j=0;j<=n-1;j++) { if(S%3==0) { ok=false; break; } S=S/3; } return ok; } /* char san[100]; char *print(int S)//将S的三进制形式存入san中,其中S的三进制高位存在san的0位置 { for(int j=0;j<=n-1;j++) { if(S%3) san[n-1-j]='0'+S%3; else san[n-1-j]='0'; S=S/3; } san ='\0'; return san; } */ inline bool in(int i,int S)//判断第i个点是否在集合S中出现至少1次 { for(int j=0;j<i;j++) S = S/3; if(S%3)return true; return false; } int dp(int i,int S)//计算d[i][S]=min{ d[j][S1]+dist[j][i] } { if(d[i][S]>=0)return d[i][S]; int &ans = d[i][S]; ans=1e9; int S1 = S-t[i];//S1为从S中去除一次i的集合状态 for(int j=0;j<n;j++)if( j!=i && in(j,S1) && dist[j][i]!=-1 )//j必须在S1中出现至少1次,且j不能为i且(i,j)之间有路 { ans = min(ans,dp(j,S1)+dist[j][i]); } //printf("d[%d][%s]=%d\n",i,print(S),ans); return ans; } int main() { int temp=1; for(int i=0;i<=10;i++)//用t[i]来保存3的i次幂 { t[i]=temp; temp *=3; } while(scanf("%d%d",&n,&m)==2&&n) { int max_3=0;//max_3最终的3进制形式为全1111111111 for(int i=0;i<n;i++) max_3 += t[i]; cnt=0; for(int S=max_3;S<t ;S++)if(legal(S))//S至少每个都出现1次,最多每个都出现两次 { state[cnt++]=S;//预先生成所有的合法状态保存起来 } memset(dist,-1,sizeof(dist));//初始假设所有边都不连通 for(int i=0;i<m;i++) { int a,b,c; scanf("%d%d%d",&a,&b,&c); a--; b--; if( dist[a]==-1 || dist[a][b]>c ) dist[a][b]=dist[b][a]= c;//保存最小的边,因为有可能有重边 } memset(d,-1,sizeof(d));//初始化表示所有d值还没计算 for(int i=0;i<n;i++) d[i][t[i]] = 0; int sum=dp(0,max_3);//sum为所求的最短距离 for(int i=0;i<n;i++)//当前所在点为 i { for(int j=0;j<cnt;j++)//从预先保存的所有合法状态中取状态 { sum = min(sum,dp(i,state[j])); } } if(sum==1e9)printf("-1\n"); else printf("%d\n",sum); } return 0; }
[b]AC代码:预处理(预先计算了base[value][p])之后递推的时间是437MS,递归记忆化搜索的时间是625MS。下面是两段代码的合并:
//利用递推做的 #include<algorithm> #include<cstring> #include<cstdio> #include<cmath> const int INF = 0x3f3f3f3f;//这是一个OJ的极限int数,哪怕+1变成0x3f3f3f40都会WA using namespace std; int d[12][60000];//d[i][S]=-1表还没计算过,=1e10表已经计算过但是没有路,>=0表计算过且有路 int n,m; int dist[15][15]; int cnt; int t[12];//t[i]=3^i 幂值 int base[60000][15];//base[x][i]=2表数x的三进制表示时第i位是2 int state[60000]; bool legal(int S)//判断S的三进制形式中是否没有一个0 { bool ok = true; for(int j=0;j<=n-1;j++) { if(S%3==0) { ok=false; break; } S=S/3; } return ok; } int main() { int temp=1; for(int i=0;i<=10;i++)//用t[i]来保存3的i次幂 { t[i]=temp; temp *=3; } memset(base,0,sizeof(base)); for(int value=0;value<t[10];value++)//用来预先生base[x][i]=2表示x的三进制第i位是2 { int temp = value; int ptr=0; while(temp) { base[value][ptr++]=temp%3; temp/=3; } } while(scanf("%d%d",&n,&m)==2&&n) { int max_3=0;//max_3最终的3进制形式为全1111111111 for(int i=0;i<n;i++) max_3 += t[i]; cnt=0; for(int S=max_3;S<t ;S++)if(legal(S))//S至少每个都出现1次,最多每个都出现两次 { state[cnt++]=S;//预先生成所有的 [合法] [最终] 状态保存起来 } memset(dist,-1,sizeof(dist));//初始假设所有边都不连通 for(int i=0;i<m;i++) { int a,b,c; scanf("%d%d%d",&a,&b,&c); a--; b--; if( dist[a][b]==-1 || dist[a][b]>c ) dist[a][b]=dist[b][a]= c;//保存最小的边,因为有可能有重边 } memset(d,0x3f,sizeof(d));//初始化表示所有d值还没计算 for(int i=0;i<n;i++) d[i][t[i]] = 0; for(int S=0;S<t ;S++)//生成子状态d[j][S],先循环S是因为: { //要保证计算父状态之前所有可能的子状态都已经计算出来了 for(int j=0;j<n;j++) { if(base[S][j]==0)//j不在S中 continue; for(int i=0;i<n;i++) { if(i==j)//前后去到的城市相同 continue; if(base[S][i]==2)//集合S已经去过i两次了 continue; if(dist[j][i]==-1)//从j到i没有路 continue; int now = S+t[i]; d[i][now] = min( d[i][now] , d[j][S]+dist[j][i] ); } } } int sum=INF;//sum为所求的最短距离 for(int i=0;i<n;i++)//当前所在点为 i { for(int j=0;j<cnt;j++)//从预先保存的所有合法状态中取状态 { sum = min(sum,d[i][state[j]]); } } if(sum==INF)printf("-1\n"); else printf("%d\n",sum); } return 0; } //利用记忆化搜索递归 /* #include<algorithm> #include<cstring> #include<cstdio> #include<cmath> using namespace std; int d[12][60000];//d[i][S]=-1表还没计算过,=1e10表已经计算过但是没有路,>=0表计算过且有路 int n,m; int dist[15][15]; int t[12]; int cnt; int state[60000]; int base[60000][15];//base[x][i]=2表数x的三进制表示时第i位是2 bool legal(int S)//判断S的三进制形式中是否没有一个0 { bool ok = true; for(int j=0;j<=n-1;j++) { if(S%3==0) { ok=false; break; } S=S/3; } return ok; } inline bool in(int i,int S)//判断第i个点是否在集合S中出现至少1次 { for(int j=0;j<i;j++) S = S/3; if(S%3)return true; return false; } int dp(int i,int S)//计算d[i][S]=min{ d[j][S1]+dist[j][i] } { if(d[i][S]>=0)return d[i][S]; int &ans = d[i][S]; ans=1e9; int S1 = S-t[i];//S1为从S中去除一次i的集合状态 for(int j=0;j<n;j++) if( j!=i && base[S1][j]!=0 && dist[j][i]!=-1 )//j必须在S1中出现至少1次,且j不能为i且(i,j)之间有路 { ans = min(ans,dp(j,S1)+dist[j][i]); } //printf("d[%d][%s]=%d\n",i,print(S),ans); return ans; } int main() { int temp=1; for(int i=0;i<=10;i++)//用t[i]来保存3的i次幂 { t[i]=temp; temp *=3; } memset(base,0,sizeof(base)); for(int value=0;value<t[10];value++)//用来预先生base[x][i]=2表示x的三进制第i位是2 { int temp = value,p=0; while(temp) { base[value][p++]=temp%3; temp/=3; } } while(scanf("%d%d",&n,&m)==2&&n) { int max_3=0;//max_3最终的3进制形式为全1111111111 for(int i=0;i<n;i++) max_3 += t[i]; cnt=0; for(int S=max_3;S<t ;S++)if(legal(S))//S至少每个都出现1次,最多每个都出现两次 { state[cnt++]=S;//预先生成所有的合法状态保存起来 } memset(dist,-1,sizeof(dist));//初始假设所有边都不连通 for(int i=0;i<m;i++) { int a,b,c; scanf("%d%d%d",&a,&b,&c); a--; b--; if( dist[a][b]==-1 || dist[a][b]>c ) dist[a][b]=dist[b][a]= c;//保存最小的边,因为有可能有重边 } memset(d,-1,sizeof(d));//初始化表示所有d值还没计算 for(int i=0;i<n;i++) d[i][t[i]] = 0; int sum=dp(0,max_3);//sum为所求的最短距离 for(int i=0;i<n;i++)//当前所在点为 i { for(int j=0;j<cnt;j++)//从预先保存的所有合法状态中取状态 { sum = min(sum,dp(i,state[j])); } } if(sum==1e9)printf("-1\n"); else printf("%d\n",sum); } return 0; } */
相关文章推荐
- 创建自定义的Cocos2d-x场景
- 在XP系统中,如何让添加新管理员帐户和原来的管理员帐户同时存在
- jQuery常用方法(一)-基础
- Yii框架操作数据库的几种方式与mysql_escape_string
- ORA-03113: end-of-file on communication channel
- 字节序
- Android Activity学习笔记——Activity的启动和创建
- 经典排序算法之——选择排序
- 二难推理
- 457 - Linear Cellular Automata
- 创建爬网规则
- 二难推理
- Delphi对象池MyObjectPool.pas
- 《C++ Primer第五版》读书笔记(7)--SEQUENTIAL CONTAINERS
- JSTL1.1函数标签库(functions)
- 【环境搭建002】ubuntu 13 在vm 下的 NFS 搭建
- PHP autoload机制详解 自动加载函数
- Mac rails console database error
- MyEclipse快捷键大全(转)1
- A*算法