倍增--luogu1081开车旅行

传送门

倍增神题????
细节超级多···
其实思路很简单
对于每个点,A和B要开去的目的地是固定的,所以就想到了倍增
开三个倍增数组,t[i][j][k]代表k从i开车走2^j天走到的那个城市
f是A走的路程,g是B走的路程
0代表A,1代表B


对于预处理,一开始怎么想怎么是n^2的
后来自己yy了一下觉得每次sort一下也行嘛!
look!就像这样

sort(a+i+1,a+n+1,cmp);
            int x=lower_bound(a+i+1,a+n+1,a[i])-a;
            if(abs(a[x].h-a[i].h)>abs(a[x-1].h-a[i].h)) x--;
            g[i][0][1]=abs(a[x].h-a[i].h); t[i][0][1]=x;
            int y;
            if(abs(a[x-1].h-a[i].h)<=abs(a[x+1].h-a[i].h)) y=x-1;
            else y=x+1;

外层i从n-1循环到1
但是我发现这样子很容易会找到它自己,好像连样例2都过不了
于是想到了排序+二分
我觉得这个其实也能过,但写到一半放弃了觉得一定有更简单的方法
这时候问了一下小伙伴他们看的是哪个题解(划掉
然后学习到了一种高端操作(其实很简单
那就是双向链表!

sort(a+1,a+n+1);//get到了双向链表大法! 
    for(int i=1;i<=n;i++) a[i].l=i-1,a[i].r=i+1,pos[a[i].id]=i;
    a[1].l=a[n].r=0;
    for(int i=1;i<=n;i++){
        aim=pos[i]; l=a[aim].l; r=a[aim].r;
        if(quq()) nb[i]=a[l].id,na[i]=qvq(a[l].l,r);//左边更近 
        else nb[i]=a[r].id,na[i]=qvq(l,a[r].r);//右边更近 
        if(l) a[l].r=r;//
        if(r) a[r].l=l;//delete
    }

它可以很方便快捷地解决掉这种有限制性的排序问题
因为每次只能从小往大走,所以找完一个小的就把它从链表删掉
这样找后面的时候一定不会找到它
又因为事先排好了序,所以只要看左边和右边哪个更小就好啦!
是不是特别简单!!!


好了,预处理解决完,就可以开始求倍增数组了
和lca的套路十分相似,也是根据2次幂,但是要注意1的时候和>1的时候不一样
所以要单独考虑一下


求好了倍增数组,这道题就做完了80%
然而我却wa在了最后求答案的地方
因为倍增的题只做过lca···
所以十分naive地以为只要贪心地找到第一个比x0小的就好了
啊我怎么那么傻啊喂,有可能还到不了x0啊
给你们看看我的傻代码:

for(int i=1;i<=n;i++)
        for(int j=17;j>=0;j--){
            if(t[i][j][0]>0 && f[i][j][0]+g[i][j][0]<=1LL*x0 && g[i][j][0]>0)
                if(ans-(double)f[i][j][0]/g[i][j][0]>eps) ans=(double)f[i][j][0]/g[i][j][0];
                else if(ans==(double)f[i][j][0]/g[i][j][0]) aim=(a[aim].h>a[i].h)?aim:i;
            else if(f[i][j][0]+g[i][j][0]<=1LL*x0 && g[i][j][0]==0 && aim==0) aim=i;
        }

还有这个:

s=rd(); x0=rd();
        for(int i=17;i>=0;i--){
            if(t[s][i][0]>0 && f[s][i][0]+g[s][i][0]<=1LL*x0){
                printf("%lld %lld\n",f[s][i][0],g[s][i][0]);
                flg=true; break;
            }
        }

这错误思想简直经典

其实正确写法应该是这样:

inline void solve(int s,int x){//哦原来找是这么个找法,跟求lca差不多的样子 
    la=lb=0; int k=0;
    for(int j=lg;j>=0;j--)
        if(t[s][j][k] && f[s][j][k]+g[s][j][k]<=x){
            x-=f[s][j][k]+g[s][j][k];
            la+=f[s][j][k], lb+=g[s][j][k];
            if(j==0) k^=1;
            s=t[s][j][k];
        }
}

看来基本上所有的倍增都是这么一个模板了0.0


说完这些整个题就已经出来了啊喂
这个看似复杂的题就被我们一步一步地解决掉了
其实所有很复杂的题都可以化成简单的小问题解决的吧qvq
接下来看一下我的AC代码!

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define maxn 100005
#define LL long long
using namespace std;
int n,m,s,x0,t[maxn][20][2],lg;//0->A 1->B
int pos[maxn],aim,l,r,na[maxn],nb[maxn];
LL g[maxn][20][2],f[maxn][20][2],ans1,ans2,la,lb;//f->A g->B

inline int rd(){
    int x=0,f=1;char c=' ';
    while(c<'0' || c>'9') {if(c=='-')f=-1;c=getchar();}
    while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
    return x*f;
}

struct qwq{
    int h,id,l,r;
    bool operator < (const qwq &x) const {
        return h<x.h;
    }
}a[maxn];
inline bool cmp1(qwq x,qwq y) {return x.id<y.id;}

inline bool quq(){
    if(!l) return false; if(!r) return true;
    return a[aim].h-a[l].h<=a[r].h-a[aim].h;
}
inline int qvq(int x,int y){
    if(!x) return a[y].id;
    if(!y) return a[x].id;
    if(a[aim].h-a[x].h<=a[y].h-a[aim].h) return a[x].id;
    return a[y].id;
}

inline void drive(){
    sort(a+1,a+n+1);//get到了双向链表大法! 
    for(int i=1;i<=n;i++) a[i].l=i-1,a[i].r=i+1,pos[a[i].id]=i;
    a[1].l=a[n].r=0;
    for(int i=1;i<=n;i++){
        aim=pos[i]; l=a[aim].l; r=a[aim].r;
        if(quq()) nb[i]=a[l].id,na[i]=qvq(a[l].l,r);//左边更近 
        else nb[i]=a[r].id,na[i]=qvq(l,a[r].r);//右边更近 
        if(l) a[l].r=r;//
        if(r) a[r].l=l;//delete
    }
    sort(a+1,a+n+1,cmp1);
    for(int i=n-1;i;i--){
        int x=nb[i],y=na[i]; 
        f[i][0][0]=abs(a[y].h-a[i].h),t[i][0][0]=y;
        g[i][0][1]=abs(a[x].h-a[i].h),t[i][0][1]=x;
        if(i==n-1){
            f[i][0][0]=0;
        }
        t[i][1][0]=t[y][0][1]; f[i][1][0]=f[i][0][0]; f[i][1][1]=f[x][0][0];
        t[i][1][1]=t[x][0][0]; g[i][1][0]=g[y][0][1]; g[i][1][1]=g[i][0][1];

    }
    for(int j=2;j<=lg;j++)
        for(int i=1;i<=n;i++)
            for(int k=0;k<=1;k++){
            if(t[i][j-1][k]) t[i][j][k]=t[t[i][j-1][k]][j-1][k];
            if(t[i][j-1][k])
                f[i][j][k]=f[t[i][j-1][k]][j-1][k]+f[i][j-1][k],
                g[i][j][k]=g[t[i][j-1][k]][j-1][k]+g[i][j-1][k];
    }
}

inline void solve(int s,int x){//哦原来找是这么个找法,跟求lca差不多的样子 
    la=lb=0; int k=0;
    for(int j=lg;j>=0;j--)
        if(t[s][j][k] && f[s][j][k]+g[s][j][k]<=x){
            x-=f[s][j][k]+g[s][j][k];
            la+=f[s][j][k], lb+=g[s][j][k];
            if(j==0) k^=1;
            s=t[s][j][k];
        }
}

int main(){
    n=rd();
    for(int i=1;i<=n;i++) a[i].h=rd(),a[i].id=i;
    lg=log(n*1.0)/log(2.0);
    drive();
    x0=rd();
    aim=0; ans1=1; ans2=0;
    for(int i=1;i<=n;i++){
        solve(i,x0);
        if(!lb) la=1;
        if(la*ans2<lb*ans1 || (la*ans2==lb*ans1 && a[i].h>a[aim].h))
            ans1=la,ans2=lb,aim=i;
    }
    printf("%d\n",aim);
    m=rd();
    while(m--){
        flg=false;
        s=rd(); x0=rd();
        solve(s,x0);
        printf("%lld %lld\n",la,lb);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值