Noip 2012 开车旅行 - 倍增DP

49 篇文章 0 订阅
3 篇文章 0 订阅

我们先用set预处理出,在每一个城市,A和B下一次分别要去的地方
那么是求对于距离每个城市最小的和次小距离的其他城市。

由于set的自动有序性,我们可以先找到位于城市的指针,然后找这个指针左边的两个,右边的两个,最小和次小距离一定在这四个数之中。

然后一个一个点模拟会T掉。。。我们可以设小A,小B各开一次车为一轮 f(i,j) f ( i , j ) 表示在第i个城市,开 2j 2 j 车会到的城市, da(i,j) d a ( i , j ) 表示小A从第i个城市开始,经历了 2j 2 j 轮,小A开车的距离, db(i,j) d b ( i , j ) 同理

倍增的核心思想是 2i1+2i1=2i 2 i − 1 + 2 i − 1 = 2 i
所以可得状态转移方程:

f(i,j)=f(f(i,j1),j1) f ( i , j ) = f ( f ( i , j − 1 ) , j − 1 )

da(i,j)=da(i,j1)+da(f(i,j1),j1) d a ( i , j ) = d a ( i , j − 1 ) + d a ( f ( i , j − 1 ) , j − 1 )

db(i,j)=db(i,j1)+db(f(i,j1),j1) d b ( i , j ) = d b ( i , j − 1 ) + d b ( f ( i , j − 1 ) , j − 1 )

da和db的方程是这样推出来的:由于那个核心思想,我们想要从i走 2j 2 j 步,就可以先走 2j1 2 j − 1 步,再走 2j1 2 j − 1 步,因此就有了 da(i,j1)+da(f(i,j1),j1) d a ( i , j − 1 ) + d a ( f ( i , j − 1 ) , j − 1 ) 。其中 f(i,j1) f ( i , j − 1 ) 表示从i走 2j1 2 j − 1 步会到达的城市。

关于倍增的预处理代码:

for(int i=1; i<=n; i++) {
        int f1 = fa[i];
        int f2 = fb[fa[i]];
        if(f1!=0) {
            da[i][0] = dda[i];
        }
        if(f2!=0) {
            db[i][0] = ddb[f1];
        }
        f[i][0] = f2;
    }
    for(int j=1; j<=16; j++) 
        for(int i=1; i<=n; i++) {
            f[i][j] = f[f[i][j-1]][j-1];
            da[i][j] = da[i][j-1] + da[f[i][j-1]][j-1];
            db[i][j] = db[i][j-1] + db[f[i][j-1]][j-1];
        }
}

另外…set的find操作有点慢,但set自带一个lower_bound(),速度大体上快于find

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <set>
using namespace std;
#define debug(x) cerr<<#x<<"="<<x<<endl;
const int MAXN = 100010, MAXM = 10010;
const int INF =0x7fffffff - 10;
int x0,xi,si,n,m,h[MAXN],sta[MAXN],stb[MAXN],cnt;
int fa[MAXN], fb[MAXN],dda[MAXN],ddb[MAXN];
int f[MAXN][20],da[MAXN][20],db[MAXN][20];
struct City{
    int id, h;
    bool operator < (const City &a) const{
        return h < a.h;
    } 
}c[MAXN];
struct temp{
    int d,id;
    bool operator <(const temp &a) const {
        if(a.d != d) 
            return d < a.d;
        else 
            return c[id].h < c[a.id].h;
    }
}tem[6];
set <City> s;
set <City> :: iterator it;
void init() {
    for(int i=n; i>=1; i--) {
        cnt = 0;
        s.insert(c[i]);
        it = s.lower_bound(c[i]);//这里本来是find函数,后来我才发现可以进一步优化
        if(it!=s.begin()) {
            it--;
            tem[++cnt] = (temp) {abs((it->h) - c[i].h),it->id};
            if(it!=s.begin()) {
                it--;
                tem[++cnt] = (temp) {abs((it->h) - c[i].h),it->id};
                it++;
            }
            it++;
        }
        if((++it)!=s.end()) {
            tem[++cnt] = (temp) {abs((it->h) - c[i].h),it->id};
            if((++it)!=s.end()) {
                tem[++cnt] = (temp) {abs((it->h) - c[i].h),it->id};
            }
        }
        sort(tem+1,tem+cnt+1);
        if(tem[1].id) {
            fb[i] = tem[1].id;
            ddb[i] = tem[1].d;
        }
        if(tem[2].id) {
            fa[i] = tem[2].id;
            dda[i] = tem[2].d;
        }
    }
    for(int i=1; i<=n; i++) {
        int f1 = fa[i];
        int f2 = fb[fa[i]];
        if(f1!=0) {
            da[i][0] = dda[i];
        }
        if(f2!=0) {
            db[i][0] = ddb[f1];
        }
        f[i][0] = f2;
    }
    for(int j=1; j<=16; j++) {
        for(int i=1; i<=n; i++) {
            if(f[f[i][j-1]][j-1]) {
                f[i][j] = f[f[i][j-1]][j-1];
                da[i][j] = da[i][j-1] + da[f[i][j-1]][j-1];
                db[i][j] = db[i][j-1] + db[f[i][j-1]][j-1];
            }
        }
    }
}
double work1(int s, int x) {
    long long xa = 0,xb = 0;
    for(int i=16; i>=0; i--) {
        if(f[s][i]&&da[s][i]+db[s][i] <= x) {
            xa += da[s][i];
            xb += db[s][i];
            x -= da[s][i] + db[s][i];
            s = f[s][i];
        }
    }
    if(fa[s]&&da[s][0]<=x){
        xa += da[s][0];
    }
    if(xb == 0) return (double)INF;
    return double(xa)/double(xb);
}
void work2(int s, int x) {
    long long xa = 0, xb = 0;
    for(int i=16; i>=0; i--) {
        if(f[s][i] && da[s][i] + db[s][i] <= x) {
            xa += da[s][i];
            xb += db[s][i];
            x-= da[s][i] + db[s][i];
            s = f[s][i];
        }
    }
    if(fa[s]&&da[s][0]<=x) {
        xa += da[s][0];
    }
    printf("%lld %lld\n", xa,xb);
}
int main() {
    scanf("%d", &n);
    for(int i=1; i<=n; i++) {
        scanf("%d", &c[i].h);
        c[i].id = i;
    }
    init();
    scanf("%d", &x0);
    double ans1 = INF+1,tem_ans;
    int ansid;
    for(int i=1; i<=n; i++) {
        tem_ans = work1(i,x0);
        if(tem_ans<ans1||(tem_ans==ans1 && c[i].h>c[ansid].h)) {
            ans1 = tem_ans;
            ansid = i;          
        }
    }
    printf("%d\n",ansid);
    scanf("%d", &m);
    for(int i=1; i<=m; i++) {
        scanf("%d %d",&si, &xi);
        work2(si,xi);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值