题解 【卢卡杨比火车站/(Nescafé杯模拟赛一)星之船】
_虹_
2019-07-10 20:44:53
好像是学校**祖传**老题来着。
据说这题原题最早是**毕克毕姥爷**出的。原题来自雀巢咖啡杯第一场,~~确实是个老题~~。
[原题link](https://max.book118.com/html/2017/0525/109015952.shtm)
#### 题面:
您被分配到了火车司机的岗位,尽管您想当文艺工作者,您的任务是去运载被肃反的老布尔什维克,并将他们流放到西伯利亚,因为“要么跟约瑟夫走,要么跟弗拉基米尔走”,尽管工业化的路线是约瑟夫从列夫那里照搬过来的,然而由于列夫被驱逐,去了墨西哥,所以工业化的细节并不优秀,也不够灵活,导致您的火车的车门位置是乱的。
我们可以认为卢卡扬比火车站的站台长度是 L,火车最左侧的门称为第 1 个门,对于满足 1 ≤ I ≤ N 的整数,火车的第 i 个门与第 1 个门之间的距离是 Di ,并且 有 0 = D1 < D2 <...< DN-1 < DN ≤ L。站台上有 M 个乘客,对于满足 1 ≤ I ≤ M 的整数,第 i 个乘客距离站台左边缘的距离为 Pi ,那么有 0 ≤P1 ≤ P2 ≤ … ≤ PM ≤ L。如果火车停在 S 这个位置,也就是说火车的第 1 个门正对着站台的位置距离站台左边缘的距离是 S,那么由于所有的门都要正对站台,显然有 0 ≤ S ≤ L - DN 。此时,火车的第 i 个门正对站台的位置就是 S + D i 。
如果第 i 个乘客从第 j 个门上车,由于火车站巨大(因为有大量人员需要被流放),门的大小可以忽略不计,那么他需要走的距离就是|Dj + S - Pi |。当然,所有的乘客都会选择需要时间最小的门上车。NKVD向您通报了停靠站台的情况,因为您被要求让乘客尽可能多走路,请你上报使得所有乘客上车距离和最长的停靠位置 S,以及此时所有乘客上车所需的距离总和。
如果存在多种满足条件的停靠方式,请输出S最小的方案。
题面latex炸了,领会精神。
#### 数据范围
对于 30%的数据 L ≤ 100; M ≤ 50; N ≤ 50。
对于 100%的数据 0< L ≤10,0000,0000; M ≤ 300; N ≤ 300。
------------
~~苏联笑话永不过时。~~ 看在慈父面子上不上原题面了,反正原题面也没啥意思,感兴趣可以去开头链接自己看。
好像能说的比赛时候都写在代码开头了。。。
```cpp
/*
对于每个人,每次对应一个门。
随着s单调变化,对应的门编号单调递减,所以最多有nm种对应关系 。
考虑上人在门的那个方向,这个也是单调的,又多n种情况。
这东西的答案是个分段函数,最多2nm段。
分段函数变化的时候至少有一个对应关系发生变化,对应关系发生变化的条件是个关于s的不等式,可以直接移项求每个对应关系变化时最小的s。
然后扔到堆里,每次取最小的s,算答案,修改对应状态就ojbk了
算答案的话,分段函数自变量只有一个次数为1的s,根据斜率求最值就行了。
当然也可以直接带入两个端点取max(当时傻了没想到)
然而考场出锅了,炸了一个int,然后整道题都炸了,100->30
行8,我先离开人世了。
考场rush的代码,还是建议看文字领会精神。
*/
#include <iostream>
#include <queue>
#include <algorithm>
using namespace std;
typedef long long ll;
const int kmaxn=300+15;
ll dwlx[kmaxn];//达瓦里希
ll glg[kmaxn];//古拉格
ll Len;
ll ans;
ll anss;
int n,m;
struct Status{
int pr;
int dir;//0 left 1 right or direct//门在人的左/右侧
};
enum DIRECTION{
L,R
};
Status st[kmaxn];
priority_queue<pair<ll,int>,vector<pair<ll,int> > ,greater<pair<ll,int> > > q;//所需s值,前进至下一个状态的同志的编号。
ll myabs(ll l)
{
return l>0?l:-l;
}
int lb(ll v)
{
return lower_bound(glg+1,glg+n+1,v)-glg;
}
ll dir_advance(int p)
{
ll d=glg[st[p].pr];
return myabs(dwlx[p]-d)+1;
}
ll sts_advance(int p)
{
if(st[p].pr<=1)return Len-glg[n]+1;
ll x=dwlx[p];
ll a=glg[st[p].pr-1];
ll b=glg[st[p].pr];
return (2*x-a-b+1)/2;
}
ll sadvance(int p)
{
if(st[p].dir==L)
return dir_advance(p);
else
return sts_advance(p);
}
ll cal_num,cal_mul;//常数项,s的系数
void do_advance(int p)
{
ll d=glg[st[p].pr];//这个d考试时候是int,然后就爆了
if(st[p].dir==L)//dir change
{
cal_num+=d;
cal_num-=dwlx[p];
cal_mul++;
st[p].dir=R;
cal_num+=d;
cal_num-=dwlx[p];
cal_mul++;
}
else//pair change
{
st[p].dir=L;
cal_num-=d;
cal_num+=dwlx[p];
cal_mul--;
d=glg[--st[p].pr];
cal_num-=d;
cal_num+=dwlx[p];
cal_mul--;
}
ll t=sadvance(p);
q.push(make_pair(t,p));
}
int main()
{
ios::sync_with_stdio(false);
cin>>Len>>m;
Len*=10;
for(int i=1;i<=m;++i)
{
cin>>dwlx[i];
dwlx[i]*=10;
}
cin>>n;
for(int i=2;i<=n;++i)
{
cin>>glg[i];
glg[i]*=10;
}
ll t1,t2;
int p;
for(int i=1;i<=m;++i)//求初始配对
{
t1=t2=Len*m;
p=lb(dwlx[i]);
if(p<=n)//right or direct
t1=myabs(glg[p]-dwlx[i]);
--p;
if(p>0)//left
t2=myabs(glg[p]-dwlx[i]);
if(t1<t2)//dwlx glg
{
st[i].dir=R;
st[i].pr=p+1;
cal_num+=glg[p+1];
cal_mul++;
cal_num-=dwlx[i];
}
else//glg dwlx
{
st[i].dir=L;
st[i].pr=p;
cal_num-=glg[p];
cal_mul--;
cal_num+=dwlx[i];
}
}
for(int i=1;i<=n;++i)//求每个点状态前进所需的s.
{
t1=sadvance(i);
q.push(make_pair(t1,i));
}
////初始状态答案
ans=0;
t1=0;
t2=q.top().first-1;
ll tans,nows;
if(cal_mul>0)
nows=t2-1;
else
nows=t1;
tans=cal_mul*nows+cal_num;
if(tans>ans){
ans=tans;
anss=nows;
}
while((t1=q.top().first)<=Len-glg[n])//range [t1,t2]
{
p=q.top().second;
q.pop();
while((t2=q.top().first)==t1)//same
{
do_advance(p);
p=q.top().second;
q.pop();
}
do_advance(p);
if(cal_mul>0)
nows=t2-1;
else
nows=t1;
tans=cal_mul*nows+cal_num;
if(tans>ans){
ans=tans;
anss=nows;
}
}
cout<<anss/10<<'.'<<anss%10<<' '<<ans/10<<'.'<<ans%10<<endl;
return 0;
}
/*
样例
4
5
0 1 2 3 4
4
1 2 3
输出
0.5 2.5
*/
```
时间复杂度nmlog(nm),约等于n^2logn,上界比较松。
出题的说std是n^3的。
```cpp
using namespace std;
int main(){}
#include<cstdio>
#include<algorithm>
#include<cctype>
#include<cmath>
const double eps=1e-1;
struct _Main{
typedef long long ll;
template <typename Type>
void read(Type &a){
char t;
while(!isdigit(t=getchar()));
a=t-'0';
while(isdigit(t=getchar())) {
a*=10;
a+=t-'0';
}
}
double left;
double ans[2];
double calc(){
double ret=0;
int i,j,k;
j=0;
for(i=0;i<m;i++){
while(j+1<n && fabs(d[j+1]+left-p[i])<fabs(d[j]+left-p[i])){
j++;
}
ret+=fabs(d[j]+left-p[i]);
}return ret;
}
int l,m,n;
int p[305],d[305];
_Main(){
double temp;
int i,j,k;
read(l);
read(m);
for(i=0;i<m;i++){
read(p[i]);
}
read(n);
for(i=1;i<n;i++){
read(d[i]);
}
left=0;
ans[0]=calc();
ans[1]=left;
left=l-d[n-1];
temp=calc();
if(temp>ans[0]){
ans[0]=temp;
ans[1]=left;
}
for(i=1;i<n;i++){
for(j=0;j<m;j++){
left=p[j]-(d[i]-d[i-1])/2.0-d[i-1];
if( left>-eps && (left +d[n-1] < l+eps )){
temp=calc();
if(temp>ans[0]+eps || ((temp>ans[0]-eps) && left<ans[1]-eps)){
ans[0]=temp;
ans[1]=left;
}
}
}
}
printf("%.1lf %.1lf",ans[1],ans[0]);
fclose(stdout);
}
}boat;
//反正出题人给的std就是这个,这是个什么东西我也不知道,我也不敢问。
//也不知道能不能运行。
```
网上唯一的一篇题解[链接](https://blog.csdn.net/MintGreenTZ/article/details/73159272),可能也是n^3的(并不确定)。
不知道之前有没有n^2logn做法,按照出题人(**并不是毕克**)的反应,可能是没有。
~~回头试试卡掉n^3算法把这题再出一遍。~~
仔细研究了这个优化的原理,准备把总用时最大出成最小值最大/最大值最小。~~然后卡掉n^3。~~