4979.海贼王的伟大航路

题目概述

POJ 4979 海贼王的伟大航路

输入
输入数据包含多行。
第一行包含一个整数N(2 < N ≤ 16),代表伟大航路上一共有N个岛屿(包含起点的罗格镇和终点的拉夫德鲁)。其中,起点的编号为1,终点的编号为N。
之后的N行每一行包含N个整数,其中,第i(1 ≤ i ≤ N)行的第j(1 ≤ j ≤ N)个整数代表从第i个岛屿出发到第j个岛屿需要的时间t(0 < t < 10000)。第i行第i个整数为0。

输出
输出为一个整数,代表路飞一行从起点遍历所有中间岛屿(不重复)之后到达终点所需要的最少的时间。

样例输入
5
0 18 13 98 8
89 0 45 78 43
22 38 0 96 12
68 19 29 0 52
95 83 21 24 0

样例输出
137

提示
对于样例输入:可能的路径及总时间为:
1,2,3,4,5: 18+45+96+52=211
1,2,4,3,5: 18+78+29+12=137
1,3,2,4,5: 13+38+78+52=181
1,3,4,2,5: 13+96+19+43=171
1,4,2,3,5: 98+19+45+12=174
1,4,3,2,5: 98+29+38+43=208
所以最短的时间花费为137
单纯的枚举在N=16时需要14!次运算,一定会超时。

DP算法分析

这道题感觉上像是DP,但是我总也想不出个具体的转移方程。
以下摘录网上别人的代码和想法。
内存2576kb,时间24ms。

  • 受到限制的有集合中元素,和最后到达的地点,与之前的顺序无关,最后从1到n也与2到n-1的顺序无关
  • 考虑状态压缩,先处理1到n-1结点,最后处理中间部分结点到n结点 d[S][i]=min(d[S][i],d[S-(1<< i)][j]+dist[j][i]);
#include<cstdio>
#include<climits>
#include<iostream>
using namespace std;
#define N 17
int d[1<<N][N],dist[N][N];
int n,i,j,S,ans;bool f;
int main(){
    scanf("%d",&n);
    for(i=0;i<n;i++)
        for(j=0;j<n;j++)
            scanf("%d",&dist[i][j]);
    n-=2;
    for(int i=0;i<n;i++)    d[(1<<i)][i]=dist[0][i+1];

    for(S=0;S<(1<<n);S++){
        for(i=0;i<n;i++) if(S==(1<<i)) f=true;
        if(f) {f=false;continue;
    }
        for(i=0;i<n;i++) d[S][i]=INT_MAX/2;
        for(i=0;i<n;i++){
            if(S&(1<<i)){
                for(int j=0;j<n;j++){
                    if(i==j) continue;
                    if(S&(1<<j))
                        d[S][i]=min(d[S][i],d[S-(1<<i)][j]+dist[j+1][i+1]);
                }
            }
        }
    }
    ans=INT_MAX,S=(1<<n)-1;
    for(int i=0;i<n;i++) ans=min(ans,d[S][i]+dist[i+1][n+1]);
    printf("%d",ans);
    return 0;
}

DFS算法分析

实际上,在作业中老师把他归到搜索的题目,所以我就想到了用DFS。然而我的代码比较拙劣,TLE了。
又搜索相关剪枝策略,贴上别人的代码。关键的部分在于以下这个nposition数组记录了[1~po][选择方式]的最优解,可以减除大量无关解。
内存2088kb,时间76ms。

#include<cstdio>
#include<string.h>
#include<stdlib.h>
#include<math.h>
const int inf = 0x3f3f3f3f;
int step;
int minstep;
int a[16][16];
int book[15];
int postion[20];
int n;
int npostion[15][1<<15];
int po;
void dfs(int cur,int qc)
{
    if(cur+2==n)
    {
        step+=a[qc][n-1];
        if(step<minstep)
            minstep=step;
        step-=a[qc][n-1];
        return;
    }
    for(int i=1; i<n-1; i++)
    {
        if(!book[i])// has been through
        {
            if(step>=minstep)
                continue;
            if((npostion[i][po+postion[i-1]]<=step+a[qc][i]))
                continue; //重要剪枝语句,我把这一句写在for前面便导致超时了
            po+=postion[i-1];
            step+=a[qc][i];
            npostion[i][po]=step;
            book[i]=1;
            dfs(cur+1,i);
            po-=postion[i-1];
            book[i]=0;
            step-=a[qc][i];
        }
    }
}
int main()
{
    for(int i=0; i<14; i++)
        postion[i]=1<<i;
    while (~scanf("%d",&n))
    {
        po=0;
        step=0;
        minstep=1e17;
        memset(npostion,inf,sizeof(npostion));
        memset(a,0,sizeof(a));
        memset(book,0,sizeof(book));
        for(int i=0; i<n; i++)
            for(int j=0; j<n; j++)
                scanf("%d",&a[i][j]);
        dfs(0,0);
        printf("%d\n",minstep);
    }
    return 0;
}

个人解法

在上面代码的指导下,我优化了一下自己的,终于通过了。
我加入了一个新的剪枝:当前的sum如果加上最小的可能(代码中实现为cut数组)超过了minimum的话则return(上边的代码是当前sum>=minimum则return),并且把step作为一个全局变量来用了。
内存1444kb,时间39ms。内存是DP的一半,时间是DP的两倍,还可以。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<bitset>
using namespace std;
#define INF 0x3f3f3f3f
int cost[20][20];
int brune[20][(1<<14)+5];
int cut[20]={0},pos[20]={0}; 
bitset<20>visit;
int minimum=INF;
int n,sum=0,step=1,state=0;
void f(int k){
    if(sum+cut[n-step]>minimum)
            return; //加上最小可能都超过最小值的话则返回
    if(step==n-1){
        minimum=min(sum+cost[k][n],minimum);
        return;
    }
    for(int i=2;i<n;++i){
        if(visit[i]==0){
            if(sum+cost[k][i]>=brune[i][state+pos[i]])
                        continue;       //注意是“>=”,最优解肯定是小于等于此的
            brune[i][state+pos[i]]=sum+cost[k][i];
              visit[i]=1;
              sum+=cost[k][i];
              ++step;
              state+=pos[i];
              f(i);
              --step;
              state-=pos[i];
              sum-=cost[k][i];
              visit[i]=0;
        }
    }
 } 
int main(){
   cin>>n;
   int b[20];
   memset(brune,INF,sizeof(brune));
   memset(b,INF,sizeof(b));
   for(int i=1;i<=n;++i){
    pos[i]=(1<<i);
   for(int j=1;j<=n;++j){ 
   cin>>cost[i][j];
   if(cost[i][j]!=0)
   b[i]=min(b[i],cost[i][j]);   
   } }
   sort(b+1,b+n+1);
   for(int i=1;i<=n;++i){
    cut[i]=b[i]+cut[i-1];   //还剩i个岛时至少还要再加上cut[i]的时间
   }
   visit[1]=1;
   state=2;
   f(1);
   cout<<minimum;
 }

总结

能尽早剪枝就剪除,留到下一步很可能就爆炸。
注意大于号和大于等于号的实际意义。
这一题大于等于就可以跳出,否则将超时。
早日掌握DP算法。
DFS的代码有点儿长,主要是全局变量挺多,有点冗余。
应该还有更精巧的DFS代码,有待发现。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容