题目:https://www.luogu.org/problemnew/show/P1081
分析:
以样例1说明:
【帮助理解——老结点编号:学生的学号;新结点编号:座位号。对座位号建立双向链表。遍历座位号以寻找最近点、次近点,这里用到学号与座位号之间的对应关系。为了避免重复,遍历一个点后,要把这个点从双向链表中删除,这样余下的点都在被删点的东边。2019/9/21】
一、寻找老结点x的最近点、次近点编号nb[x]、na[x]
一)先建立双向链表,并建立排序先后编号之间的对应关系,用p数组保存。
sort前 | i | 1 | 2 | 3 | 4 |
d[i].w | 2 | 3 | 1 | 4 | |
d[i].id | 1 | 2 | 3 | 4 | |
sort后 | i | 1 | 2 | 3 | 4 |
d[i].w | 1 | 2 | 3 | 4 | |
d[i].id | 3 | 1 | 2 | 4 | |
p[d[i].id] | 1 | 2 | 3 | 4 | |
d[i].l | 0 | 1 | 2 | 3 | |
d[i].r | 2 | 3 | 4 | 0 | |
规定d[0].l=d[0].r=0 |
例如:老结点1在新序列中的编号为2 (p[1]=2 ),左链指向老结点3,右链指向老结点2,左链的左链指向0,右链的右链指向4。
二)先确定最近点位置,判断是左结点l还是右结点r,这里可以引入函数left(),代码如下:
bool left(){//取左侧还是右侧
if(!l) return 0;
if(!r) return 1;
return d[j].v-d[l].v<=d[r].v-d[j].v;
}
如果左结点为0,则直接取右结点(不管右结点是否为0);
然后如果右结点为0,则直接到左结点;
如果左、右结点都不为0,则再判断距离远近。
即有四种情况:
1、 0 x 0
2、 0 x r
3、 l x 0
4、 l x r
三)再确定次近点在左还是右,代码如下:
int judge(int a,int b){//隐含d[0].l=d[0].r=0。故不存在漏洞
if(!a) return d[b].i;
if(!b) return d[a].i;
if(d[j].v-d[a].v<=d[b].v-d[j].v)return d[a].i;
else return d[b].i;
}
其中的a对应在左侧的老结点编号,b对应在右侧的。理解方式同一)。
以下面例子说明一)与二)
对于链表 0 0 n 0 0
因为n的左l是0,故nb[n]=r,即nb[n]=0。然后a=l[n],b=r[r[n]],即a=0,b=0,因为a=0,所以取b,从而na[n]=0。
四)在双向链表中删除当前结点,以保证余下双链的点都在已经删除点的东边。
二、倍增
【f[i][j]表示以点i(学号)为起点经2^j轮后到达的点。2019/9/21】
一)倍增初始化
for(i=1;i<=n;i++){
f[i][0]=nb[na[i]];//小A先走小B再走,一起算一步
stA[i][0]=abs(d[p[i]].v-d[p[na[i]]].v);
stB[i][0]=abs(d[p[f[i][0]]].v-d[p[na[i]]].v);
}
二)倍增dp
void deal_first(){
int i,j;
for(j=1;j<=19;j++)
for(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 query(int x,int p){
int i;
a=b=0;
for(i=19;i>=0;i--)
if(f[p][i] && (a+b+stA[p][i]+stB[p][i]<=x)){
//关键:f[p][i]
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];
//关键:na[p]
}
关键点:如果结点f[p][i]=0,或者na[p]=0,则路径已经中断,不用再增加开车距离。
所以,需要在建立双向链表时,要求规定d[0].l=d[0].r=0。
四、怎样处理小B的开车距离是0的问题,根据题目条件,只要规定输出的ans=n即可,如果所有的b=0,答案就是n,否则,会更新ans。
AC代码(代码转自洛谷题解,只作了很小很小一点点的修改):
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
const int Maxn=1e5+5;
int n,m,l,r,j;//l左,r右,j当前
struct node{
int i,v,l,r;
bool operator < (const node & b) const{
return v<b.v;
}
}d[Maxn];
int p[Maxn];//用于保存sort后新编号对应的sort前的老编号
int stA[Maxn][21],stB[Maxn][21],f[Maxn][21];
int na[Maxn],nb[Maxn],a,b,ans;
double minn=2e10;
bool left(){//取左侧还是右侧
if(!l) return 0;
if(!r) return 1;
return d[j].v-d[l].v<=d[r].v-d[j].v;
}
int judge(int a,int b){//隐含d[0].l=d[0].r=0。故不存在漏洞
if(!a) return d[b].i;
if(!b) return d[a].i;
if(d[j].v-d[a].v<=d[b].v-d[j].v)return d[a].i;
else return d[b].i;
}
void deal_first(){
int i,j;
for(j=1;j<=19;j++)
for(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 query(int x,int p){
int i;
a=b=0;
for(i=19;i>=0;i--)
if(f[p][i] && (a+b+stA[p][i]+stB[p][i]<=x)){
//关键:f[p][i]
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];
//关键:na[p]
}
int main(){
int i;
int x;
cin>>n;
for(int i=1;i<=n;i++){
scanf("%d",&d[i].v);
d[i].i=i;
}
sort(d+1,d+n+1);
for(int i=1;i<=n;i++){
p[d[i].i]=i;
d[i].l=i-1;d[i].r=i+1;
}
d[1].l=d[n].r=0;//重要技巧。隐含d[0].l=d[0].r=0。
for(int i=1;i<=n;i++){
j=p[i];l=d[j].l;r=d[j].r;
if(left()){
nb[i]=d[l].i;na[i]=judge(d[l].l,r);
}
else {
nb[i]=d[r].i;na[i]=judge(l,d[r].r);
}
if(l) d[l].r=r;//删除当前新结点j
if(r) d[r].l=l;
}
for(i=1;i<=n;i++){
f[i][0]=nb[na[i]];//小A先走小B再走,一起算一步
stA[i][0]=abs(d[p[i]].v-d[p[na[i]]].v);
stB[i][0]=abs(d[p[f[i][0]]].v-d[p[na[i]]].v);
}
deal_first();
cin>>x;
ans=n;//如果所有的b=0,则输出n
for(i=1;i<=n;i++){
query(x,i);
if(b && 1.0*a/b < minn){
minn=1.0*a/b;
ans=i;
}
}
cout<<ans<<endl;
cin>>m;
while(m--){
scanf("%d%d",&j,&x);
query(x,j);
printf("%d %d\n",a,b);
}
return 0;
}