FJNU 1176 汪老司机(DP or 建图+最短路)

Posted Blackops

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FJNU 1176 汪老司机(DP or 建图+最短路)相关的知识,希望对你有一定的参考价值。

1196: 汪老司机

Time Limit: 1000 MS         Memory Limit: 257792 KB
64-bit interger IO format: %lld        Java class name: Main
Prev  Next 

 

汪老司机是实验室出了名的老司机,早在大一就拿到了驾照,每年的暑假他都会带家人开车出游,今年的暑假也不例外,汪老司机今年准备带家人去平潭游玩,汪老司机的家离平潭有两条路,每条路都存在n个路段,两条路的n个路段长度都相同,两条路的每个路段都分别存在一个耗油量ai和bi(走完第i个路段,两条路分别耗油ai和bi),因为汪老司机是个熟练的老司机,因此他可以在任意一条路上变道到另外一条路上(只允许在路段的端点变道),虽然是老司机但是他也不能变道超过k次,因为这样容易被警察发现,汪老司机想知道他最少需要耗费多少油才能到达平潭。
注意:当汪老司机在家时,他可以选择两条路的任意一条路作为起点但不消耗他的变道次数

Input

包含多组样例,第一行一个数字T,表示样例的个数
对于每组样例:
第一行两个个数字n,k,分别表示道路路段的个数和能够变道的最多次数
第二行n个数字ai表示第一条道路不同路段的耗油量
第三行n个数字bi表示第二条道路不同路段的耗油量
( T <= 25, 1 <= n <= 10000, 0 <= k <= 10, 0 <= ai <= 1e9, 0 <= bi <= 1e9 )

Output

对于每组样例输出一行一个数字,表示最少的耗费量

Sample Input

1
6 5
1 4 5 8 9 12
2 3 6 7 10 11

Sample Output

36

HINT

汪老司机行走的路线:a1 -> b2 -> a3 -> b4 -> a5 -> b6

 

题目链接:FJNU 1196

当时比赛的时候想到的是用最短路然而一直WA,后来下午补题室友提醒了一发说是用一个二维数组来做DP,然后突然就醒悟了……虽然我DP做的题不多,但是这种模型我见过很多,用DP[n][k]表示走过了前n条路变换k次车道的最小耗油量,然后……对!还差一个状态表示,那就再加一维变成DP[n][k][s]表示过了前n条路变换k次车道的最小耗油量且当前车子在s道上,然后很顺地写下来就1A了(鸡冻1s)。然而还是感觉我最短路的建模灰常完美没有任何问题啊,直到今晚写了个对拍发现了问题。

先说一下我当时最短路的建模吧,其实做过拆点题或者隐式图的题都很容易想到,虚拟一个原点S(0),每一个点都是一条边,那么显然每一个点周围应有两个虚拟节点,因此上半部分是$[1,n+1]$;同理下面的视为$[n+2,n*2+2]$,然后S连到上路的起点1,下路的起点n+2均为一条距离为0的边,那么转弯就可以看成是上面到点到下面的对称点之间的双向边,边权也显然均为0($i <=> i+n+1$),然后边里当然要加一个记录是否是变道路的变量flag,然后跑一个带限制的SPFA即可。

但是这样做是有问题的,比如这组数据

 

10
10 1
7 5 6 2 8 6 8 1 9 4
3 2 8 4 5 6 8 4 7 3

程序出来是46,正确答案是49,模拟一番发现居然变道了两次,但是程序里明明写着当转弯次数>k时直接continue不可能会转两次的啊,最后发现其实是dis数组的缘故,因为平时习惯了用一维的dis数组来表示单源最短距离,其实错误的答案并不是变道两次造成的,而是路径中某一个点本身是从上面的点扩展而来,不需经过变道即可获得最短路径,但是却被后面更新的路错误地拼接到了拓展队列里,简单地说这一条距离为46的路径是不存在的,是由两条路径错误地拼接而成大概如下图(画的比较抽象,只需要感性的认识一下……)

 

其中上面红色的终点是被拓展了,但是你下面的蓝色又拓展到对称点的时候又反过来利用了红色的信息,而且此时蓝色状态k=0,错误地进行了拓展。

那么如何解决呢?我是增加了一维dis[n][k]表示第n个点变道k次的最短距离,这样的好处就是状态更新的时候不会错误地拿本身不衔接的状态进行更新。然后就过了。

再说一下DP解法吧,写出来其实是一个比较简单的递推DP,递推方程:

 

\\begin{cases}dp[n][k][0]=min(dp[n-1][k][0]+a[i],dp[n-1][k-1][1]+b[i]), \\\\dp[n][k][1]=min(dp[n-1][k][1]+b[i],dp[n-1][k-1][0]+a[i])
, \\end{cases}

 

DP代码:

#include <stdio.h>
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define CLR(arr,val) memset(arr,val,sizeof(arr))
#define LC(x) (x<<1)
#define RC(x) ((x<<1)+1)
#define MID(x,y) ((x+y)>>1)
typedef pair<int,int> pii;
typedef long long LL;
const double PI=acos(-1.0);
const int N=10010;
LL a[N],b[N];
LL dp[N][12][2];
 
int main(void)
{
    int tcase,i,j,n,k;
    scanf("%d",&tcase);
    while (tcase--)
    {
        CLR(dp,0);
        scanf("%d%d",&n,&k);
        for (i=1; i<=n; ++i)
            scanf("%lld",&a[i]);
        for (i=1; i<=n; ++i)
            scanf("%lld",&b[i]);
        dp[1][0][0]=a[1];
        dp[1][0][1]=b[1];
        for (i=1; i<=n; ++i)
        {
            for (j=0; j<=k; ++j)
            {
                if(j==0)
                {
                    dp[i][0][0]=dp[i-1][0][0]+a[i];
                    dp[i][0][1]=dp[i-1][0][1]+b[i];
                }
                else
                {
                    dp[i][j][0]=min<LL>(dp[i-1][j][0]+a[i],dp[i-1][j-1][1]+a[i]);
                    dp[i][j][1]=min<LL>(dp[i-1][j][1]+b[i],dp[i-1][j-1][0]+b[i]);
                }
            }
        }
        LL ans=1e20;
        for (i=0; i<=k; ++i)
            ans=min<LL>(ans,min<LL>(dp[n][i][0],dp[n][i][1]));
        printf("%lld\\n",ans);
    }
    return 0;
}

 


 

 

最短路代码:(cin取消同步之后居然比scanf快……服了)

#include <stdio.h>
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define CLR(arr,val) memset(arr,val,sizeof(arr))
#define LC(x) (x<<1)
#define RC(x) ((x<<1)+1)
#define MID(x,y) ((x+y)>>1)
typedef pair<int,int> pii;
typedef long long LL;
const double PI=acos(-1.0);
const int N=10100;
struct edge
{
    int to,nxt;
    LL w;
    int flag;
};
edge E[N<<2];
int head[N<<1],tot;
LL a[N],b[N],d[N<<1][11];
int n,k;
bool vis[N<<1][11];

void init()
{
    CLR(head,-1);
    tot=0;
    CLR(d,INF);
    CLR(vis,false);
}
inline void add(int s,int t,LL d,int f)
{
    E[tot].to=t;
    E[tot].flag=f;
    E[tot].w=d;
    E[tot].nxt=head[s];
    head[s]=tot++;
}
void spfa(int s)
{
    vis[s][0]=1;
    d[s][0]=0LL;
    queue<pii>Q;
    Q.push(pii(s,0));
    while (!Q.empty())
    {
        int now=Q.front().first;
        int nk=Q.front().second;
        Q.pop();
        vis[now][nk]=0;
        for (int i=head[now]; ~i; i=E[i].nxt)
        {
            int v=E[i].to;
            int vk=nk+E[i].flag;
            if(vk>k)
                continue;
            if(d[v][vk]>d[now][nk]+E[i].w)
            {
                d[v][vk]=d[now][nk]+E[i].w;
                if(!vis[v][vk])
                {
                    vis[v][vk]=1;
                    Q.push(pii(v,vk));
                }
            }
        }
    }
}
int main(void)
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int tcase,i;
    cin>>tcase;
    while (tcase--)
    {
        init();
        cin>>n>>k;
        for (i=1; i<=n; ++i)
            cin>>a[i];
        for (i=1; i<=n; ++i)
            cin>>b[i];
        int S=0;
        for (i=1; i<=n; ++i)
            add(i,i+1,a[i],0);
        for (i=1; i<=n; ++i)
            add(n+i+1,n+2+i,b[i],0);
        for (i=1; i<=n+1; ++i)
        {
            add(i,n+i+1,0LL,1);
            add(n+i+1,i,0LL,1);
        }
        add(S,1,0LL,0);
        add(S,n+2,0LL,0);
        spfa(0);
        LL minm=d[(N<<1)-1][0];
        for (i=0; i<=k; ++i)
            minm=min(minm,d[n+1][i]);
        for (i=0; i<=k; ++i)
            minm=min(minm,d[2*n+2][i]);
        cout<<minm<<endl;
    }
    return 0;
}

以上是关于FJNU 1176 汪老司机(DP or 建图+最短路)的主要内容,如果未能解决你的问题,请参考以下文章

HDU 1176 免费馅饼:dp

HDU 1176 经典dp

hdu3499(分层图最短路 or 反向建图)

hdu-1176 免费馅饼

[HDU1176]免费馅饼(DP)

hdoj1176 免费馅饼(dp 数塔)