洛谷P1081 开车旅行——快排,双向链表,原函数反函数,倍增

题目:https://www.luogu.org/problemnew/show/P1081

分析:

以样例1说明:

【帮助理解——老结点编号:学生的学号;新结点编号:座位号。对座位号建立双向链表。遍历座位号以寻找最近点、次近点,这里用到学号与座位号之间的对应关系。为了避免重复,遍历一个点后,要把这个点从双向链表中删除,这样余下的点都在被删点的东边。2019/9/21】

一、寻找老结点x的最近点、次近点编号nb[x]、na[x]

一)先建立双向链表,并建立排序先后编号之间的对应关系,用p数组保存。

sort前i1234
 d[i].w2314
 d[i].id1234
      
sort后i1234
 d[i].w1234
 d[i].id3124
 p[d[i].id]1234
 d[i].l0123
 d[i].r2340
 规定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;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值