开车旅行|倍增优化dp+双端列表/set|题解
题面:
题面链接
这题的思路值得借鉴,也是我做的第一道倍增优化dp题目.
比较好的是题目的意思较为清晰,所以不再赘述.
解析:
这里我们可以非常直接的想到暴力模拟,因为第一眼看上去前七个点的数据范围是支持我们进行一个简单的预处理得到对应人在对应位置的决策的.
(排序O(n×sqrt(n))+对应位置决策处理O(n^2)).
这样之后模拟每个节点出发的全过程,以及它查询的各个信息的全过程,就可以得到对应答案了.
然后就考虑剩下的30怎么拿,其实我们既然有预处理的想法,就可以明白,既然我们在某一个点上两个人的决策是固定的且绑定的.
我们的起点,行走最大距离也固定,那么就可以用一个st表来优化我们的过程,走的距离大于对应距离就不跳,小于等于就转移.
这样到最后可以得到两个人在固定节点跳固定距离的最后终点与两个人行走的距离.
(由于两人操作绑定,在st表(sta)中用i表示在i点a跳一步的距离,在stb中用i表示a跳完之后b再跳一步的距离,而f存由i跳两步后的终点).
#include<bits/stdc++.h>
#define ll long long
#define qr qr()
#define pa pair<int,int>
#define fr first
#define sc second
#define ve vector<int>
#define lc tree[rt].ls
#define rc tree[rt].rs
#define Man What_can_I_say
using namespace std;
const int N=2e5+200,MN=50000;
double minn=2147483647;
int n,m,j,l,r,tot,p[N],head[N],sta[N][20],stb[N][20],f[N][20],na[N],nb[N],a,b,ans;
struct Man{
int id,h,l,r;
bool operator <(const Man & a) const
{
return h<a.h;
}
}ct[N<<2];
inline int qr
{
int x=0;char ch=getchar();bool f=0;
while(ch>57||ch<48)
{
if(ch=='-')f=1;
ch=getchar();
}
while(ch<=57&&ch>=48)x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return f?-x:x;
}
bool check()
{
if(!l) return 0;
if(!r) return 1;
return ct[j].h-ct[l].h<=ct[r].h-ct[j].h;
}
int pd(int a,int b)
{
if(!a) return ct[b].id;
if(!b) return ct[a].id;
if(ct[j].h-ct[a].h<=ct[b].h-ct[j].h) return ct[a].id;
return ct[b].id;
}
void pre_st()
{
for(int j=1;j<=19;++j)
{
for(int i=1;i<=n;++i)
{
f[i][j]=f[f[i][j-1]][j-1];
sta[i][j]=sta[i][j-1]+sta[f[i][j-1]][j-1];
stb[i][j]=stb[i][j-1]+stb[f[i][j-1]][j-1];
}
}
}
void getab(ll x,int p)
{//这里其实就是我们所说的dp过程,其实也算不上dp吧.
a=0,b=0;
for(int i=19;i>=0;--i)
{
if(f[p][i]&&(a+b+sta[p][i]+stb[p][i])<=x)
{
a+=sta[p][i];
b+=stb[p][i];
p=f[p][i];
}
}
if(na[p]&&a+b+sta[p][0]<=x)a+=sta[p][0];//还能再让a跳一步就让它跳.
}
void init()
{
n=qr;
ll x;
for(int i=1;i<=n;++i)ct[i].h=qr;
for(int i=1;i<=n;++i)ct[i].id=i;
sort(ct+1,ct+n+1);
for(int i=1;i<=n;++i)p[ct[i].id]=i;//离散化,找到每个原先的城市在排序后的位置
for(int i=1;i<=n;++i)ct[i].l=i-1,ct[i].r=i+1;
ct[1].l=ct[n].r=0;
for(int i=1;i<=n;++i)
{//这步是预处理的一部分,它是处理出每个位置上两个人的决策.
j=p[i];l=ct[j].l;r=ct[j].r;
if(check())nb[i]=ct[l].id,na[i]=pd(ct[l].l,r);
else nb[i]=ct[r].id,na[i]=pd(l,ct[r].r);
if(l) ct[l].r=r;//删掉一个节点
if(r) ct[r].l=l;
}
for(int i=1;i<=n;++i)
{//每个人的决策记录到st表对应位置.
//注意一点就好,我们的stb数组它存储的是这个点上经过两步决策(两个人分别跑一步)之后b的行走距离,不是在i这个点上b一次决策的行走距离
//因为我们调用的时候的第一步都是a来走,所以把b的操作也绑定在一起是方便的.
f[i][0]=nb[na[i]];
sta[i][0]=abs(ct[p[i]].h-ct[p[na[i]]].h);
stb[i][0]=abs(ct[p[f[i][0]]].h-ct[p[na[i]]].h);
}
pre_st();
x=qr,m=qr;
for(int i=1;i<=n;++i)
{
getab(x,i);
if(b&&1.0*a/b<minn)
{
minn=1.0*a/b;
ans=i;
}
}
printf("%d\n",ans);
for(int i=1;i<=m;++i)
{
j=qr,x=qr;
getab(x,j);
printf("%d %d\n",a,b);
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.in","r",stdin);
freopen("out.out","w",stdout);
#endif
init();
return 0;
}
后记:
其实值得说的是双端列表,这里也是我第一次使用.
感觉上还是比较好理解的,用两个指针指向前后元素,删除插入操作只用动指针,排序后的系列操作用O(1)的优良复杂度解决.
好用.
另外是set解法,有时间会再加以补充.
https://www.cnblogs.com/shining-like-stars