P1081[NOIP2012提高组]开车旅行
前两天老师还让我们狂做紫题,为什么今天就要求我们对这一道蓝题打暴力qwq
updata : 今天突然看到,这道题也是紫题了qwq
P1081 开车旅行
这道题一看就是dp一类的题,然后就会很顺畅的想到倍增awa
首先,看一下暴力怎么打。
这道题是当年的T4,然后有整整70分的暴力分,这是十分可观的awa。
所以,先想一下暴力。很容易想到先预处理,再模拟。
当然,如果你看不懂或是完全就不想看我写的这依托答辩,你可以直接去想想正解
以下是代码:
#include<bits/stdc++.h>
using namespace std;
int n,m;
long long x,a,b,s;
bool kk=0;
long long h[100005],l[100005],ll[100005];
long long netA[100005],netB[100005];
double Min=2000000009;
int main()
{
h[0]=0x3f3f3f3f3f3f3f3f;
cin>>n;
for(int i=1;i<=n;i++) cin>>h[i];
for(int i=n-1;i>=1;i--)
{
long long mmin=i+1,mmin2=0;
l[i]=abs(h[i]-h[i+1]);
for(int j=i+2;j<=n;j++)
{
if(l[i]>abs(h[i]-h[j])||(l[i]==abs(h[i]-h[j])&&h[j]<h[mmin]))
{
ll[i]=l[i];
l[i]=abs(h[i]-h[j]);
mmin2=mmin;
mmin=j;
}
else if(ll[i]==0||ll[i]>abs(h[i]-h[j])||(ll[i]==abs(h[i]-h[j])&&h[j]<h[mmin2]))
{
ll[i]=abs(h[i]-h[j]);
mmin2=j;
}
}
netA[i]=mmin;
netB[i]=mmin2;
}
long long ans=0;
cin>>x;
for(int i=1;i<=n;i++)
{
a=b=kk=0;
long long fl=i;
while(1)
{
if(kk)
{
if(a+b+l[fl]>x||!netA[fl]) break;
b+=l[fl],fl=netA[fl];
}
else
{
if(a+b+ll[fl]>x||!netB[fl]) break;
a+=ll[fl],fl=netB[fl];
}
kk^=1;
}
if(!ans||1.0*a/b-Min<-1e-9||(fabs(1.0*a/b-Min)<=1e-9&&h[ans]<h[i]))
Min=1.0*a/b,ans=i;
}
cout<<ans<<'\n';
cin>>m;
while(m--)
{
a=b=kk=0;
cin>>s>>x;
while(1)
{
if(kk)
{
if(a+b+l[s]>x||!netA[s]) break;
b+=l[s],s=netA[s];
}
else
{
if(a+b+ll[s]>x||!netB[s]) break;
a+=ll[s],s=netB[s];
}
kk^=1;
}
cout<<a<<" "<<b<<'\n';
}
return 0;
}
还是很容易看懂的吧awa?
好了,暴力已经打完了,我们已经有了70分,那么我们现在来想一下正解。
所以就开始想正解 $ dp $ :
首先,我们会发现 $ n,m $ 都很大,所以肯定是要预处理一部分内容,然后 $ O(1) $ 或者是 $ O(log n) $ 解答。
然后会发现,$ O(1) $ 是行不通的,因为 $ O(1) $ 一般是把一部分内容离线下来或者是直接对于所有的情况进行解答,然而 $ x $ 是每次给定的,并且范围是 $ 1e9 $ ,且没有共同性,所以不行。
那我们就来想一想别的方法:$ O(log n) $ 的方法。
可以发现,每一个点将会转移到的下一个点是已经固定的,然后还要$ log n $ 转移+找出答案,所以我们就可以很自然的想到倍增。
让我们来想一下怎么设 $ dp $ 状态:
设 $ f[i][j].x $ 为 $ 小A $ 从第 $ i $ 个点开始,向后驾车 $ 2^{j} $ 轮,能到哪里。
设 $ f[i][j].a $ 为$ 小A $ 从第 $ i $ 个点开始,向后驾车 $2^{j} $ 轮,$ 小A $ 开了多远。
设 f[i][j].b 为小A从第i个点开始,向后驾车 \(2^{j}\) 轮,小B开了多远。
插一下数组解释:
$ netA[i] $ : 由 $ i $ 出发, $ 小A $ 驾车,下一个目的地是哪个点。
$ netB[i] $ : 由 $ i $ 出发, $ 小B $ 驾车,下一个目的地是哪个点。
转移显然:
$ f[i][j].x=f[i+f[i][j-1].x][j-1].x; $
$ f[i][j].a=f[i][j-1].a + f[i+f[i][j-1].x][j-1].a; $
$ f[i][j].b=f[i][j-1].b + f[i+f[i][j-1].x][j-1].b; $
但是!!!
有问题吗?
当然有
仔细思考一下,这个转移式子其实是有很大的问题的
其它的都还好,因为都是2的好几次方
但 $ f[i][0] $ 的意思是往后开一轮,这时,下一轮的司机就换成了 $ 小B $ 。
可状态里是都是 $ 小A $ 开始开车 $ qwq $ 。
所以问题大了 $ QAQ $
那么我们可以变一下 dp 定义:
设 $ f[0/1][i][j] $ 为从i这个点出发,向后驾车 $ 2^{j} $ 轮的各种状态
然后 $ [0/1] $ 代表:
$ 0 :小A $ 先开始开车
$ 1 :小B $ 先开始开车
那我们对于第二维等于 $ 0 $ 和 $ 1 $ 时特殊处理一下:
$ f[0][i][0].w=netA[i]; $
$ f[1][i][0].w=netB[i]; $
$ f[0][i][0].a=abs(h[netA[i]]-h[i]); $
$ f[1][i][0].b=abs(h[netB[i]]-h[i]); $
$ f[0][1][1].w=f[1][f[0][i][0].w][0].w;$
$ f[0][1][1].A=f[0][i][0].A+f[1][f[0][i][0].w][0].A;$
$ f[0][1][1].B=f[0][i][0].B+f[1][f[0][i][0].w][0].B;$
对于 $ j>=2 $ ,就只需要处理 $ f[0][i][j] $就可以了awa
$ f[0][i][j].w=f[0][i+f[0][i][j-1].x][j-1].w; $
$ f[0][i][j].a=f[0][i][j-1].a + f[0][i+f[0][i][j-1].w][j-1].a; $
$ f[0][i][j].b=f[0][i][j-1].b + f[0][i+f[0][i][j-1].w][j-1].b; $
这样就好了awa
然后这道题就结束了吗?
显然没有。
你仔细看一下我们打的暴力你就会发现:
我们在处理 $ netA[i] $ 和 $ netB[i] $ 时的复杂度时 $ O(n^2) $ 的,
所以我们也要将这里加速。
这里我的处理方法是倒序枚举,然后利用 $ set $ 查找前驱和后继。
最后:
代码如下:
#include<bits/stdc++.h>
using namespace std;
int n,m,ss,x0,w;
long long x;
long long hh;
double aans=-1;
int lt,nt;
long long lh,nh;
long long h[100005];
struct node{
long long A,B;
int w;
}f[2][100005][21];
struct nnode{
int bh;
long long h;
}nn;
bool operator<(nnode A,nnode B){
return A.h<B.h;
}
set<nnode>s;
void init()
{
h[0]=5e9;h[n+1]=-5e9;
nn.bh=0; nn.h=5e9;
s.insert(nn); s.insert(nn);
nn.bh=n+1; nn.h=-5e9;
s.insert(nn); s.insert(nn);
nn.bh=n; nn.h=h[n];
s.insert(nn);
for(int i=n-1;i>=1;i--)
{
int ga,gb;
nn.bh=i; nn.h=h[i];
s.insert(nn);
set<nnode>::iterator it=s.lower_bound(nn);
it--;
lt=(*it).bh; lh=(*it).h;
it++,it++;
nt=(*it).bh; nh=(*it).h;
it--;
if(abs(nh-h[i])>=abs(h[i]-lh))
{
gb=lt;
it--,it--;
if(abs(nh-h[i])>=abs(h[i]-(*it).h)) ga=(*it).bh;
else ga=nt;
}
else
{
gb=nt;
it++,it++;
if(abs((*it).h-h[i])>=abs(h[i]-lh)) ga=lt;
else ga=(*it).bh;
}
f[0][i][0].w=ga;
f[1][i][0].w=gb;
f[0][i][0].A=abs(h[ga]-h[i]);
f[1][i][0].B=abs(h[gb]-h[i]);
}
for(int i=1;i<=n;i++)
{
f[0][i][1].w=f[1][f[0][i][0].w][0].w;
f[0][i][1].A=f[0][i][0].A+f[1][f[0][i][0].w][0].A;
f[0][i][1].B=f[0][i][0].B+f[1][f[0][i][0].w][0].B;
}
for(int j=2;j<=20;j++)
{
for(int i=1;i<=n;i++)
{
f[0][i][j].w=f[0][f[0][i][j-1].w][j-1].w;
f[0][i][j].A=f[0][i][j-1].A+f[0][f[0][i][j-1].w][j-1].A;
f[0][i][j].B=f[0][i][j-1].B+f[0][f[0][i][j-1].w][j-1].B;
}
}
}
node cl(int s,long long x)
{
node now={0,0,0};
for(int j=20;j>=0;j--)
{
if(f[0][s][j].w&&now.A+now.B+f[0][s][j].A+f[0][s][j].B<=x)
{
now.A+=f[0][s][j].A;
now.B+=f[0][s][j].B;
s=f[0][s][j].w;
}
}
return now;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>h[i];
init();
cin>>x0;
for(int i=1;i<=n;i++)
{
node ans=cl(i,x0);
double C=((double)ans.A)/((double)(max(1ll,ans.B)));
if(aans==-1) w=i,hh=h[i],aans=(ans.B==0?5e9:C);
else if(ans.B!=0)
{
if(aans>C) aans=C,w=i,hh=h[i];
else if(aans==C&&h[i]>hh) w=i,hh=h[i];
}
else if(aans==5e9&&h[i]>hh) w=i,hh=h[i];
}
cout<<w<<'\n';
cin>>m;
for(int i=1;i<=m;i++)
{
cin>>ss>>x;
node ans=cl(ss,x);
printf("%lld %lld\n",ans.A,ans.B);
}
return 0;
}