• 摆渡车


    #include<bits/stdc++.h>
    using namespace std;
    int n,m,t2;
    int ans=0x3f3f3f3f,a[100000000],t1,t[100000000],f[100000000];
    int main(){
    scanf("%d%d",&n,&m); 
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%d",&t1);
    		a[t1]++;
    		t[t1]+=t1;
    		t2=max(t2,t1);
    	}
    	for(int i=1;i<t2+m;i++)
    	{
    		a[i]+=a[i-1];
    		t[i]+=t[i-1];
    	}
    	for(int i=0;i<t2+m;i++)
    	{
    		if(i>=m&&a[i-m]==a[i])
    		{
    			f[i]=f[i-m];
    			continue;
    		}
    		f[i]=a[i]*i/*需要用多少次i这个车停靠的点*/-t[i];// 特判(-∞, i] 单独作为一段的边界情况; 
    		for(int j=max(i-2*m+1,0);j<=i-m;j++)
    		{
    			f[i]=min(f[i],f[j]+(a[i]-a[j])*i-(t[i]-t[j])); 
    		}
    	}
    	for(int i=t2;i<t2+m;i++)
    	{
    		ans=min(ans,f[i]);
    	}
    	cout<<ans;
    	return 0;
    }
    一道异常bt的dp,考试的时候完全找不到思路与表达式,所幸现在做出来了,思路如下:

    我们把这一条时间轴分成几段,最后一段边界为i,j,再设k为在第k时刻,其中j<k<=i,f[i]即为本题状态,即0到i中这一段的人们总候车时间。f[j]为上一个状态,不断用j更新i嘛,都是这样的,注意i,j是随时都在变的。

    首先我们要知道这一段的长度,有人会想如果长度是<m(m是车的一趟来回)(最开始我也这样想)。but it is fool,我们把这个时间轴分成几段是吧,我们所划分的就是(我们假想的车一来一回,偶尔为了最优还要等几分钟)这种为一段,那么这一来一回就必然有了m,所以长度是>=m,说白了就是模拟,稍微想想就知道了。

    我们已知j<k<=i,我们可以这样想,j,i即车到了的状态(即上一次出发和这一次出发),因为每一个时刻车都有可能因为不同的计划,安排调整,所以每一个时刻都有可能有车出现,反正出发时间随便你调,所以这是肯定的,每一个f[i]都表示车到了后,j到i这一段的总候车时间加上f[j]这一段的总候车时间。而我们要求f[i],j到i有i-j个k,(注意不加一原因请看j<k<=i)那么求第k时刻的候车时间(k到i注意直接用i-k乘以k时刻的人数),然后不断更新k值就ok了,注意还要加上f[j]。

    这就是那个转移式的理解了,难点在于边界的随机划分,i,j不是很好理解。

    只要这样想,我们求的是f[i],我们在求f[i]的时候,之前还有i-m个状态(其实不是i-m个,后面会提到,i-m个状态怎么出现的请看上文)。

    每一个状态都有可能到达f[i],我们把那些状态统统看成f[j],每一个之前最优的f[j]再加上j到i的候车时间得到i个f[i],不断更新f[i]得到最小f[i],说白了就是分成两部分球f[i]。由f[j]的局部最优慢慢到f[i],到i达到边界时就会成为全局最优了。(只能说能想出这个的人真的厉害,我也只是看懂了,而且是暂时的,凭我的记忆力,过几天很可能又忘记怎么写了。)

    当然这只是关于表达式的理解,还有很多优化,以及一些细节需要讲解。

    首先就是求出每一个f[i]的临界值,即只在i点停下,前面的人都给我等着的这种极端情况。

    为什么要求?因为这些情况在i比较小的时候是很有可能出现的啊。

    然后想一想,怎么求i到j的人们的总等候时间?聪明的你想出来了吗?
    这里我们用到了前缀和。求总等候时间不外乎就是一遍一遍的在j<k<=i中,一直k时刻的人数*(i-k),k不断更新,且不断累加,是吧。那么总的来说j到i有多少个人就要有多少个i来减是吧,我们可以在输入数据的时候直接求出到i时刻时候的人数减去到j时刻的人数,就是i到j的总人数,就知道需要多少个i了,即k时刻的人数*(i-k)打开后k时刻的人数*i这一个式子的含义。那么k时刻的人数*k又怎么表示呢?我们发现,不断累加后,k时刻的人数*k即为i到j这一段人们开始等车时间的总和。所以可以用前缀和,用i时刻人们开始等车时间的总和减去j时刻人们开始等车时间的总和即可得到。

    代码实现f[j]+(a[i]-a[j])*i-(t[i]-t[j]。
    至于输入时的前缀和怎么打大家看代码就能懂了。
    但这样会超时
    接下来是优化
    首先是
    if(i>=m&&a[i-m]==a[i])
    		{
    			f[i]=f[i-m];
    			continue;
    		}
    到最后如果i到i+m这一段根本就没人了,还算他干什么。
    由j=0优化成j=max(i-2*m+1,0),why?
    剪枝。
    i-m为j最大的情况(原因上面已经不能再详细了。),那么i-2m+1这个点即为有意义的j的最小情况。
    j是上一趟嘛,i是这一趟嘛,你两趟之间等待时间总不能超过运行时间m吧。超过了那么必然有其他最优解(比如我不间断走两趟)来代替这一情况使等待时间最小。
    所以完全可以不算了,用那些f[j]算出的答案是必然要舍弃的。
    最后看边界
    	for(int i=t2;i<t2+m;i++)
    	{
    		ans=min(ans,f[i]);
    	}
    即看这几种要么刚好在t2时刻出发的情况和t2+m时刻这之间出发哪一种人们等待时间最少,因为是都运完了所有人的不同时刻的各自的最优选择,不一定刚好t2那个是最优的,因为虽然t2时刻的人没有等,但可能你会让前面时刻的人多等很多,所以需要比较。
    这可以说是手把手幼儿式一对一辅导。
    如果看了我的题解都做不出的人我只能为你默哀。
  • 相关阅读:
    136. 只出现一次的数字
    Eclipse Git Pull报 cannot open git-upload-pack错误的解决方案
    数据结构和算法1 稀疏数组
    Netty学习二 TCP粘包拆包以及Netty解决TCP粘包拆包
    Java值传递和引用传递
    Git命令教程
    Properties文件载入工具类
    有序的properties的工具类
    对象操作工具类
    反射工具类
  • 原文地址:https://www.cnblogs.com/xxmxxm/p/10806124.html
Copyright © 2020-2023  润新知