https://www.luogu.org/problemnew/show/P1081
给定
个城市以及它们的海拔高度
,定义两个城市之间的距离为
,只能从编号小的城市走向编号大的城市
。
现在有两个人小 和小 ,它们有一种特殊的癖好
- 小
总是走向距离当前城市
第二近
的城市 - 小
总是走向距离当前城市
第一近
的城市
假设第一天是小 开车,之后小 和小 交错开车
小 或小 在以下两种情况会停下来
- 当它们行驶的路程超过一个给定的值
- 它们已经到达第 个城市
第一问:给定
,要求找到一个出发城市,使得从这个城市出发后小A行驶的路程:小B行驶的路程
最小,找出这个城市
第二问:给定
组询问,每次询问给定一个出发城市
和
,求出从S出发,行驶总路程不超过X时,小A和小B分别的行驶距离
数据范围:
对于30%的数据,有
对于40%的数据,有
对于50%的数据,有
对于70%的数据,有
对于100%的数据,有
对于100%的数据,有
首先我们首先要知道小
和小
从第
个城市出发会去往的下一个城市
分别设为
和
两种求法:(代码用的是链表的方法)
- 建一棵平衡树,依次插入每个 ,查询它的前驱和后继即可
- 用链表的方法,保存每排序后的数原来对应的位置,因为排了序,所以我们也可以用类似的方法查询最小值和次小值
这两种求法的复杂度都是 ,其中第二种方法的瓶颈在于排序
接下来我们考虑求解本题的关键信息:如果我能够知道从某个城市出发 天会到哪个城市,并且小 和小 此时行驶的距离就能求解本题了
由于我们处理出了 和 ,所以可以直接
但。。。这样我们 的时空复杂度都是 的!只能通过70%的数据
实际上,我们可以只求出从某个城市出发 天会到哪个城市以及此时两人的行驶距离即可,因为所有的正整数都可以被分解为2整数幂次和
这样我们就可以 进行 啦
方程?
设 表示行驶了 天,当前在第 个城市,0表示小 开,1表示小 开
初始化:
设
表示上一次是谁开车,容易发现,只有
的时候,
,否则
(因为2的正整数次幂都是偶数,只有2的0次幂是奇数)
动态转移:
到这儿我们已经愉快了从哪出发开任意天( 可以组合成任何正整数天)的城市是哪,我们也可以用同样的方法处理出距离
设 表示从 城市出发,行驶了 天时,且谁在开车时,小 和小 走的距离
初始化:
动态转移:
的意义同上,这里不多赘述
到了这里问题已经成功解决了大部分了,现在考虑如何计算距离不超过 时小 和小 分别行驶的距离
拼凑法!
倒序循环 ,试着用行驶 天时的行驶距离拼凑,能拼就拼,因为边权非负,所以 和 必然单调递减,所以我们拼凑出的值必然是最优的
然后就 了
时间复杂度?
#include<set>
#include<cstdio>
#include<cctype>
#include<algorithm>
using namespace std;int n,m,x0,x,s,ga[100001],gb[100001],j,l,r,pos[100001],f[21][100001][2],ans;
long long da[21][100001][2],db[21][100001][2],la,lb;
double minn=1e17;
bool o;
struct node{int i,l,r;long long h;}p[100001];
inline bool cmp(node x,node y){return x.h<y.h;}
inline bool zuo()//判断是否左边的更优
{
if(!l) return 0;
if(!r) return 1;
return p[j].h-p[l].h<=p[r].h-p[j].h;
}
inline int nearly(int a,int b)//比较a,b谁离j更近,返回更近的那个的原序号
{
if(!a) return p[b].i;
if(!b) return p[a].i;
if(p[j].h-p[a].h<=p[b].h-p[j].h) return p[a].i;
return p[b].i;
}
inline long long dist(int a,int b)//a和b对应的距离
{
return abs(p[pos[a]].h-p[pos[b]].h);//因为是排了序的,所以要用pos
}
inline void solve(long long x,int st)//拼凑
{
la=lb=0;o=0;
for(register int i=20;i>=0;i--)
if(f[i][st][o]&&la+lb+da[i][st][o]+db[i][st][o]<=x)//若还没走完且距离满足
{
la+=da[i][st][o];lb+=db[i][st][o];if(i==0) o^=1;st=f[i][st][o];//将其拼上
}
return;
}
inline long long read()
{
char c;int d=1;long long f=0;
while(c=getchar(),!isdigit(c))if(c==45)d=-1;f=(f<<3)+(f<<1)+c-48;
while(c=getchar(),isdigit(c)) f=(f<<3)+(f<<1)+c-48;
return d*f;
}
inline void write(long long x){if(x>9)write(x/10);putchar(x%10+48);return;}//以上为读写优化
signed main()
{
freopen("1.txt","r",stdin);
n=read();
for(register int i=1;i<=n;i++) p[i].h=read(),p[i].i=i;//输入
sort(p+1,p+1+n,cmp);//排序
for(register int i=1;i<=n;i++) p[i].l=i-1,p[i].r=i+1,pos[p[i].i]=i;//链表日常
p[1].l=0;p[n].r=0;//初始化
for(register int i=1;i<=n;i++)//这里是求ga和gb
{
j=pos[i];l=p[j].l;r=p[j].r;
if(zuo()) ga[i]=nearly(p[l].l,r),gb[i]=p[l].i;
else ga[i]=nearly(l,p[r].r),gb[i]=p[r].i;
if(l) p[l].r=r;
if(r) p[r].l=l;//记得删除
}
for(register int i=1;i<=n;i++)//初始化第一天
{
if(ga[i]) f[0][i][0]=ga[i],da[0][i][0]=dist(i,ga[i]);
if(gb[i]) f[0][i][1]=gb[i],db[0][i][1]=dist(i,gb[i]);
da[0][i][1]=db[0][i][0]=0;
}
for(register int i=1;i<=20;i++)//以下为动态转移
for(register int j=1;j<=n;j++)
for(register int k=0;k<2;k++)
{
if(i==1) o=k^1;else o=k;
if(f[i-1][j][k]) f[i][j][k]=f[i-1][f[i-1][j][k]][o];
if(f[i][j][k])
da[i][j][k]=da[i-1][j][k]+da[i-1][f[i-1][j][k]][o],
db[i][j][k]=db[i-1][j][k]+db[i-1][f[i-1][j][k]][o];
}
x0=read();m=read();
for(register int i=1;i<=n;i++)
{
solve(x0,i);
if(lb&&1.0*la/lb<minn)
{
minn=1.0*la/lb;
ans=i;
}
}
write(ans);putchar(10);
while(m--)
{
j=read();x=read();
solve(x,j);
write(la);putchar(32);write(lb);putchar(10);
}
}