2018普及组摆渡车洛谷5017(记忆化搜索)

题目描述

有 𝑛 名同学要乘坐摆渡车从人大附中前往 名同学要乘坐摆渡车从人大附中前往 名同学要乘坐摆渡车从人大附中前往 名同学要乘坐摆渡车从人大附中前往 人民大学 ,第 𝑖 位同学在第 𝑡𝑖 分钟去 等车 。只有一辆摆渡车在工作 ,但摆渡车容量可以视为无限大 ,但摆渡车容量可以视为无限大 。摆渡车从人大附中出发、 把车上的 车上的 同学送到人民大、再回附中 同学送到人民大、再回附中 同学送到人民大、再回附中 同学送到人民大、再回附中 同学送到人民大、再回附中 同学送到人民大、再回附中 同学送到人民大、再回附中 (去接其他同学 去接其他同学 ),这样往返一趟 ),这样往返一趟 ),这样往返一趟 ),这样往返一趟 总共花 总共花 费 𝑚 分钟 (同学上下车时间忽略不计)。 (同学上下车时间忽略不计)。 摆渡车要将所有同学 都送到人民大摆渡车要将所有同学 都送到人民大。
凯很好奇,如果 凯很好奇,如果 他能任意安排 摆渡车出发的时间, 那么 这些同学的等车时间之和 最小为多少呢 ?
注意:摆渡车回到人大附中后可以即刻出发。
【输入 格式 】
输入文件名为 bus .in 。
第一行包含两个正整数 𝑛,m,以一个空格分开,别代表 ,以一个空格分开别代表 ,以一个空格分开别代表 等车人数和摆渡往返 一趟的时间。
第二行 包含 𝑛 个正整数, 个正整数, 相邻两数之间 相邻两数之间 相邻两数之间 以一个 空格分隔, 空格分隔, 空格分隔, 第 i 个非负 整数 𝑡𝑖 代 表第 i 个同学到达车站的时刻。
【输出 格式 】
输出文件名为 bus .out 。
输出 一行,个整数表示 所有同学等车 时间 之和的最小值 (单位: 分钟) 。

【输入出样例 1】
bus .in
bus .out
5 1
3 4 5
0
见选手目录下的 bus /bus 1.in 和 bus /busbus 1.ans 。
【输入出样例 1说明】
同学 1 和同学 4 在第 3 分钟开始等车, 等待 0 分钟, 在第 3 分钟 乘坐摆渡车 乘坐摆渡车 出发。 摆渡车 在第 4 分钟回到 人大附中 。
同学 2 和同学 3 在第 4 分钟开始等车,待 0 分钟,在第 4 分钟乘坐摆渡车 分钟乘坐摆渡车 出发。摆渡车在第 5 分钟回到人大附中。
同学 5 在第 5 分钟开始等车,待 分钟开始等车,待 0 分钟,在第 分钟,在第 分钟,在第 5 分钟乘坐摆渡车出发。 自此 所有同学 都被送到人民大学 。总等待时间为 0。

【输入出样例 2】
bus .in
bus .out
5
11 13 1 5
4
见选手目录下的 bus /bus 2.in 和 bus /busbus 2.ans 。

【输入出样例 2说明】
同学 3 在第 1 分钟开始等车,待 分钟开始等车,待 0 分钟 ,在第 1 分钟乘坐摆渡 车出发。分钟乘坐摆渡 车出发。分钟乘坐摆渡 车出发。车在第 6 分钟回到人大附中。
同学 4 和同学 5 在第 5 分钟开始等车,待 1 分钟 ,在第 6 分钟乘坐摆渡车 分钟乘坐摆渡车 出发。摆渡车在第 11 分钟回到人大附中。
同学 1 在第 11 分钟开始等车,待 分钟开始等车,待 分钟开始等车,待 分钟开始等车,待 分钟开始等车,待 分钟开始等车,待 分钟开始等车,待 2 分钟;同学 分钟;同学 分钟;同学 分钟;同学 2 在第 13 分钟开始等车, 分钟开始等车, 分钟开始等车, 分钟开始等车, 分钟开始等车, 等待 0 分钟。他 分钟。他 /她们在第 13 分钟乘坐摆渡车出发。 分钟乘坐摆渡车出发。 自此所有同学都被送到人民大。 总等待时间为 4。可以证明,没有总等待时间小于 4 的方案。
【输入出样例 3】
见选手目录下的 bus /bus 3.in 和 bus /busbus 3.ans 。
【数据规模与约定】
对于 10% 的数据, 𝑛≤10,𝑚=1,0≤𝑡𝑖≤100。
对于 30% 的数据, 𝑛≤20,𝑚≤2,0≤𝑡𝑖≤100。
对于 50% 的数据, 𝑛≤500,𝑚≤100,0≤𝑡𝑖≤104。
另有 20% 的数据, 𝑛≤500,𝑚≤10,0≤𝑡𝑖≤4×106。
对于 100% 的数据, 𝑛≤500,𝑚≤100,0≤𝑡𝑖≤4×106。

没想到普及组的题也这么难呢(好吧,是我太弱了)。这道题一开始是打算打dp来着,但是dp的定义和状态转移方程过于难想,没有想到,在考试时就打了个爆搜想要得个部分分,然鹅还是因为我太弱了的原因,就在dfs时以时间作为状态,结果当然是时间妥妥地炸了,只得了10分(好像以每个人来深搜然后不加记忆化是可以得30分来着,然鹅我太蠢了没有想到hhh)

#include<bits/stdc++.h>
using namespace std;
int timer[503],tong[4000003];
int ans=210000000,n,m;
void dfs(int t,int sum,int tot)//分别是时间,在这个时候多少人在等,已经等待了的时间和 
{
    if(tot>=ans) return ;//剪枝 
    if(t>=timer[n]) //时间到最后一个人的到达时间就可以停止了 
    {
        ans=min(ans,tot);
        return ;
    }
    //下面就分为上车和不上车
    
    //上车 
    sum=min(0,sum);//等待的人全部上车,所以sum清0 
    int pp=tot;
    for(int i=t+1;i<=t+m-1;i++)
    {
        if(tong[i]) sum+=tong[i],pp+=tong[i]*(m-i+t);//在车的来回m的时间内,可能又来了新的人,加入等待中 
    }
    dfs(t+m,sum,pp);//时间+m,注意时间和不要直接在tot上加减,不然回溯时可能出问题
    //不上车 
    if(tong[t+1]) //下一刻有人到达 
    {
        sum+=tong[t+1];//加入等待 
        dfs(t+1,sum,tot+sum-tong[t+1]);//在t+1的这一时间新加入的人不对等待时间做出贡献,但又确实加入了等待
        //所以tot减去tong[t+1](注意理解,主要是思想吧,有更好的打法也行啦) 
    }
    else dfs(t+1,sum,tot+sum);//没有新的人到达 
    
}
int main()
{
    //freopen("bus.in","r",stdin);
    //freopen("bus.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&timer[i]);
        tong[timer[i]]++;//开一个桶去存某个时间是不是某某的到达时间,有几个 
    }
      
    if(m==1){printf("0\n"); return 0;}
    sort(timer+1,timer+1+n);//排序
    dfs(1,1,0);
    printf("%d",ans); 
}
/*
5 1
3 4 4 3 5

5 5
11 13 1 5 5
*/

然鹅大佬的人是这样想的:dfs(i,st)意思是第i个人在st时间时已经积累了的等待时间(具体看代码吧hhh)(借鉴了洛谷5017题解中chen_zhe大佬的代码)

#include<bits/stdc++.h>
using namespace std;
int read()//快读 
{
    int x=0,f=1;char ch=getchar();
    while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
    return x*f;
}

int n,m,t[505];//mem[i][j]第i个人等待j的时间(注意是等待的时间) 

//因为0<=st-t[i]<=m,因此可以记忆化,把这个作为状态的第二维 

int solve(int i,int st)//记忆化搜索。i:第i个人,st:开始时间st 
{
    if (i==n+1)//所有人都上车了 
        return 0;//便不再有等待时间了 
    if (st<t[i])//如果现在的时间没有人,就到下一个人的到达时间 
        return solve(i,t[i]);
    //这一句和上一句的顺序不能换,不然数组越界会RE    int sum=0,j=i;//j从i开始 
    //车等人 即车到的时间在这个人的后面 
    while (j<=n && t[j]<=st)//车一到,该上的人就上,就不再等了 
        sum+=t[j++];//这里j++所以下面是j,而再下面是j+1 
    int best=st*(j-i)-sum+solve(j,st+m);//i到j这一段的再加上后面的(可以看做是一个累加过程) 
    //人等车 即人到的时间在车的后面
    for (;j<=n;j++)//让车再等一下,多加后面的人上车 
    {//j一直枚举到n,连把人等完 
        sum+=t[j];
        best=min(t[j]*(j-i+1)-sum+solve(j+1,t[j]+m),best);
    }
    return best;
}
int main()
{
    //memset(mem,-1,sizeof(mem));
    n=read(),m=read();
    for (int i=1;i<=n;i++)
        t[i]=read();
    sort(t+1,t+n+1);//显然从小到大按照时间排序更好算 
    printf("%d",solve(1,0));
    return 0;
}

这样仿佛就可以得30分啦啦啦

然后加一个记忆化就可以A掉这道题,下面是记忆化

#include<bits/stdc++.h>
using namespace std;
int read()//快读 
{
    int x=0,f=1;char ch=getchar();
    while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
    return x*f;
}

int n,m,t[505],mem[505][505];//mem[i][j]第i个人等待j的时间 

//因为0<=st-t[i]<=m,因此可以记忆化,把这个作为状态的第二维 

int solve(int i,int st)//记忆化搜索。i:第i个人,st:开始时间st 
{
    if (i==n+1)//所有人都上车了 
        return 0;//便不再有等待时间了 
    if (st<t[i])//如果现在的时间没有人,就到下一个人的到达时间 
        return solve(i,t[i]);
    //这一句和上一句的顺序不能换,不然数组越界会RE    
    if (mem[i][st-t[i]])//记忆化 
        return mem[i][st-t[i]];
    int sum=0,j=i;//j从i开始 
    //车等人 即车到的时间在这个人的后面 
    while (j<=n && t[j]<=st)//车一到,该上的人就上,就不再等了 
        sum+=t[j++];//这里j++所以下面是j,而再下面是j+1 
    int best=st*(j-i)-sum+solve(j,st+m);//i到j这一段的再加上后面的(可以看做是一个累加过程) 
    //人等车 即人到的时间在车的后面
    for (;j<=n;j++)//让车再等一下,多加后面的人上车 
    {//j一直枚举到n,连把人等完 
        sum+=t[j];
        best=min(t[j]*(j-i+1)-sum+solve(j+1,t[j]+m),best);
    }
    return mem[i][st-t[i]]=best;
}
int main()
{
    //memset(mem,-1,sizeof(mem));
    n=read(),m=read();
    for (int i=1;i<=n;i++)
        t[i]=read();
    sort(t+1,t+n+1);//显然从小到大按照时间排序更好算 
    printf("%d",solve(1,0));
    return 0;
}

总之,这道题主要是注意理解思想,特别是人等车和车等人那部分要取min,然后dp的做法我还没看呢(hhh)

有什么问题欢迎指正

(纪念一下第一篇博客嘻嘻嘻)

posted @ 2019-02-14 17:35  yyys  阅读(864)  评论(2)    收藏  举报