洛谷 P1081 [NOIP提高组 2012] 开车旅行(线段树+离散化+树上倍增)

题目送命门


题目解法

本题困扰了我一天,所以我要写个总结纪念纪念。

首先,这题的难点再于如何将这又臭又长的题面转换一下。我用了40min左右的时间认真思考,科学探究,最后发现这就是个树上倍增。很容易发现每个城市如果确定了是A或B开车的话,那么,他们一路向东的路径是确定的,如果我们建立一个虚点,将汽车停掉的那个点连向虚点,毫无疑问,就出来了一棵树,每个点只有一个父亲(除了虚点),然后我们就相当于问一个点在那条树链上走,最多走到哪,并把路径长度交错记下来。于是我们就将一个点拆成两个,分别代表A往上连边的点和B往上连边的点。找到每个点的父亲,构出整棵树。(将本题转换为树是解题的关键,不过应该也有其他基于链的作法,直接倍增)

虽然我yy出了这样一个麻烦的作法,代码写了250多行,调了n个小时,但是我还是要继续BB下去,将基于树上倍增的作法讲完。

很明显,我们求在一条树链上能走多远,如果一步一步枚举就太慢了,于是构出树以及边权后直接倍增。我们记两个数组, dep[i] 表示走到虚点(就是停车)位置A和B开车的总路程,这个是拿来与限制距离 x 比较的,另外,由于要知道A和B各自开了多远(两个老司机,交替驾驶不疲劳),于是我们多开一个数组 dA[] 记录A的开车路程。然后树上倍增,求出来,后面的询问都迎刃而解了(两个子任务是一样的,如果能求出每次A,B行驶距离的话)。
这里的时间是 O(mlogn) 的。

现在的问题在于如何快速求一个点在树中的父亲,就是某个城市的下一个城市是哪里?蒟蒻我思维江化,一心只想着nlogn数据结构,于是就写了个线段树+离散化,成功让我的代码变得长长长。我们将城市按海拔排序后,每个城市离散海拔,然后每次就在线段树中找第一个大于当前海拔的城市,第一个小于的,第二个大于的,和第二个小于的,然后搞一搞。注意这里有很多细节,我的程序一开始在这里错漏百出,将坐标和排名混淆的不像话(fw反向映射大法好),最后还好调出来了,诸多细节就请见代码了(rank还是改成Rank比较安全,可能CE)。我们用线段树维护最左边的1,就将所有的清为oo,取最小值就行了,维护最右边的1也一样,主要是二叉查找没有这个好写。从后往前做一遍,不断插入,就可以 O(nlogn) 建出图。

然而这里可以不必如此麻烦,直接用双向链表记一下(i-1,i-2,i+1,i+2),一边做,一边动态修改链表,从左往右的话就删除元素,然后也可以很快求出来,比线段树少了个log。我写线段树就权当练习吧。我的链表学得太差了,整天想着一维数据结构里的线段树和平衡树,链表队列这些基础数据结构不能忘啊,NOIP经常就考,一考就送。

这题就这样切掉了,用了我挺多时间,证明我的调试能力有待提高,但还是要循序渐进。写代码很快,调试很久又有什么用。Think twice,code once. 这句话再怎么强调都不为过。就这样吧。


Code

#include 
#include 
#include 
#include 
#include 
#include 
#define maxn 100100
#define oo 0x7fffffff
#define Eps 1e-10

using namespace std;

typedef long long LL;

int n, m, s, ansx;
int head_p[maxn<<1], cur = -1, Rank[maxn];
int que[maxn<<1], head, tail, fw[maxn];
LL dep[maxn<<1], dA[maxn<<1], x0, xx;
int f[20][maxn<<1];
double rate = oo;

struct Adj{
    int next, obj;
    LL len;
}Edg[maxn<<1];

struct City{
    LL height;
    int id;
}cc[maxn];

struct Tnode{
    int Minid, Maxid;
}tree[maxn<<2];

bool cmp1(City A, City B){
    return A.height < B.height;
}

bool cmp2(City A, City B){
    return A.id < B.id;
}

void build(int root, int L, int R){
    if(L == R){
        tree[root].Minid = tree[root].Maxid = -1;
        return;
    }

    int mid = (L + R) >> 1, Lson = root << 1, Rson = root << 1 | 1;

    build(Lson, L, mid);
    build(Rson, mid+1, R);

    tree[root].Minid = tree[root].Maxid = -1;
}

void update(int root, int L, int R, int x, int id){
    if(x > R || x < L)  return;
    if(L == x && x == R){
        tree[root].Minid = tree[root].Maxid = id;
        return;
    }

    int mid = (L + R) >> 1, Lson = root << 1, Rson = root << 1 | 1;
    update(Lson, L, mid, x, id);
    update(Rson, mid+1, R, x, id);

    if(tree[Lson].Minid == -1)  tree[root].Minid = tree[Rson].Minid;
    else if(tree[Rson].Minid == -1)  tree[root].Minid = tree[Lson].Minid;
    else  tree[root].Minid = min(tree[Lson].Minid, tree[Rson].Minid);

    if(tree[Lson].Maxid == -1)  tree[root].Maxid = tree[Rson].Maxid;
    else if(tree[Rson].Maxid == -1)  tree[root].Maxid = tree[Lson].Maxid;
    else  tree[root].Maxid = max(tree[Lson].Maxid, tree[Rson].Maxid);   
}

int query(int root, int L, int R, int x, int y, int sign){
    if(x > R || y < L)  return -1;
    if(x <= L && y >= R){
        if(!sign)  return tree[root].Minid;
        else  return tree[root].Maxid;
    }

    int mid = (L + R) >> 1, Lson = root << 1, Rson = root << 1 | 1;

    int temp1 = query(Lson, L, mid, x, y, sign);
    int temp2 = query(Rson, mid+1, R, x, y, sign);

    if(temp1 == -1)  return temp2;
    else if(temp2 == -1)  return temp1;
    else{
        if(!sign)  return min(temp1, temp2);
        else  return max(temp1, temp2);
    }
}

void Insert(int a, int b, LL c){
    cur ++;
    Edg[cur].next = head_p[a];
    Edg[cur].obj = b;
    Edg[cur].len = c;
    head_p[a] = cur;
}

void Da(){
    memset(dep, -1, sizeof(dep));
    dep[0] = 0;
    dA[0] = 0;
    f[0][0] = 0;
    que[0] = 0;
    while(head <= tail){
        int now = que[head++];
        for(int i = head_p[now]; ~ i; i = Edg[i].next){
            int v = Edg[i].obj;
            LL l = Edg[i].len;
            if(~ dep[v])  continue;
            dep[v] = dep[now] + l;
            dA[v] = dA[now];
            if(v > n)  dA[v] += l;
            f[0][v] = now;
            que[++tail] = v;
        }
    }

    for(int i = 1; i <= 19; i++)
        for(int j = 0; j <= (n<<1); j++)
            f[i][j] = f[i-1][f[i-1][j]];
}

LL get_d(int x, int y){
    return abs(cc[x].height - cc[y].height);
}

int main(){

    freopen("lgP1081.in", "r", stdin);
    freopen("lgP1081.out", "w", stdout);

    scanf("%d", &n);
    for(int i = 1; i <= n; i++){
        scanf("%lld", &cc[i].height);
        cc[i].id = i;
    }

    for(int i = 0; i <= (n<<1); i++)  head_p[i] = -1;

    sort(cc+1, cc+n+1, cmp1);

    Rank[cc[1].id] = 1;
    for(int i = 2; i <= n; i++)
        Rank[cc[i].id] = Rank[cc[i-1].id] + 1;

    for(int i = 1; i <= n; i++)  fw[Rank[i]] = i;

    sort(cc+1, cc+n+1, cmp2);

    build(1, 1, n);

    for(int i = n; i > 0; i--){
        int temp1 = query(1, 1, n, Rank[i]+1, n, 0);
        int temp2 = query(1, 1, n, 1, Rank[i]-1, 1);

        if(temp1 == -1 && temp2 == -1){
            Insert(0, i, 0);
            Insert(0, i+n, 0);
        }

        else if(temp1 == -1 && temp2 != -1){
            Insert(fw[temp2]+n, i, get_d(i, fw[temp2]));
            temp1 = query(1, 1, n, 1, temp2-1, 1);
            if(temp1 == -1)  Insert(0, i+n, 0);
            else  Insert(fw[temp1], i+n, get_d(i, fw[temp1]));
        }

        else if(temp1 != -1 && temp2 == -1){
            Insert(fw[temp1]+n, i, get_d(i, fw[temp1]));
            temp2 = query(1, 1, n, temp1+1, n, 0);
            if(temp2 == -1)  Insert(0, i+n, 0);
            else  Insert(fw[temp2], i+n, get_d(i, fw[temp2]));
        }

        else{
            int temp3;
            if(get_d(i, fw[temp1]) < get_d(i, fw[temp2])){
                Insert(fw[temp1]+n, i, get_d(i, fw[temp1]));
                temp3 = query(1, 1, n, temp1+1, n, 0);
                if(~ temp3 && get_d(i, fw[temp3]) < get_d(i, fw[temp2]))  temp2 = temp3;
                Insert(fw[temp2], i+n, get_d(i, fw[temp2]));
            }
            else{
                Insert(fw[temp2]+n, i, get_d(i, fw[temp2]));
                temp3 = query(1, 1, n, 1, temp2-1, 1);
                if(~ temp3 && get_d(i, fw[temp3]) <= get_d(i, fw[temp1]))  temp1 = temp3;
                Insert(fw[temp1], i+n, get_d(i, fw[temp1]));
            }
        }
        update(1, 1, n, Rank[i], Rank[i]);
    }

    Da();

    scanf("%lld", &xx);

    for(int i = 1; i <= n; i++){
        int now = i + n;
        x0 = xx;
        for(int j = 19; j >= 0; j--){
            LL distance = dep[now] - dep[f[j][now]];
            if(distance <= x0){
                now = f[j][now];
                x0 -= distance;
            }
        }

        LL sA = dA[i+n] - dA[now], sB = dep[i+n] - dep[now] - dA[i+n] + dA[now];
        if(sB == 0){
            if(rate == oo && cc[i].height > cc[ansx].height)  ansx = i;
            continue;
        }

        else if(rate == oo){
            ansx = i;
            rate = (double)sA / sB;
        }
        else if((double)sA / sB - rate < -Eps || ((fabs((double)sA / sB - rate) < Eps) && cc[i].height > cc[ansx].height)){
            ansx = i;
            rate = (double)sA / sB;
        }
    }

    printf("%d\n", ansx);

    scanf("%d", &m);

    for(int i = 1; i <= m; i++){
        scanf("%d%lld", &s, &x0);
        s += n;
        int now = s;
        for(int j = 19; j >= 0; j--){
            LL distance = dep[now] - dep[f[j][now]];
            if(distance <= x0){
                now = f[j][now];
                x0 -= distance;
            }
        }
        LL sA = dA[s] - dA[now], sB = dep[s] - dep[now] - dA[s] + dA[now];
        printf("%lld %lld\n", sA, sB);
    }

    return 0;
} 

走过那个转角 仿佛便会回到
配合彼此步幅漫步的当初
拱形的樱花树
如今已是满树橘红

——《光芒》

你可能感兴趣的:(非可持久化数据结构,倍增)