[Codevs 1199][NOIP 2012提高组]开车旅行

题目连接:http://codevs.cn/problem/1199/

这个题如果使用O(n^2)级别的思路是不可取的,首先预处理很麻烦,要用一个平衡二叉树(set实现或手写)求出每个城市距离第一小和第二小的城市编号,具体做法是在每个城市对应树中定点的前驱和前驱的前驱还有后继和后继的后继里找最小和次小的顶点编号。

然后用倍增动规完成这道题。

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#include <set>
#include <map>
#include <cmath>

#define MAXN 100005
#define MAXP 18
#define LL long long
#define INF 5000000000LL

using namespace std;

struct data //保存城市数据
{
    LL dec;
    LL h; //城市编号、高度
}city[5];

map<LL,int>hash; //建立高度映射城市编号的哈希表
set<LL>bst; //平衡二叉树,找每个城市的最小和次小差值的城市

int to[MAXN][MAXP]; //to[i][j]=城市i经过2^j个轮回后到达的城市编号
int h[MAXN]; //每个城市的高度
int n;
LL va[MAXN][MAXP];
LL vb[MAXN][MAXP];
int fa[MAXN];
int fb[MAXN];
LL a[MAXN],b[MAXN];
//va[i][j]=从城市i出发,A经过2^j步走的距离
//vb[i][j]=从城市i出发,B经过2^j步走的距离
//a[i][j]=从城市i出发,A经过2^j步走到的城市
//b[i][j]=从城市i出发,B经过2^j步走到的城市

bool operator<(data a,data b)
{
    if(a.dec!=b.dec) return a.dec<b.dec;
    return a.h<b.h;
}

void preWork() //用平衡二叉树预处理得到每个城市的最小和次小差值的城市
{
    for(int i=n;i>=1;i--) //由于车子只能从之前的城市移动到后面的城市,所以得倒着找前驱后继
    {
        bst.insert(h[i]);
        city[1].h=*--bst.lower_bound(h[i]),city[2].h=*bst.upper_bound(h[i]);
        if(city[1].h>-INF) city[3].h=*--bst.lower_bound(city[1].h);
        else city[3].h=-INF;
        if(city[2].h<INF) city[4].h=*bst.upper_bound(city[2].h);
        else city[4].h=INF;
        for(int k=1;k<=4;k++)
            city[k].dec=abs(city[k].h-h[i]);
        sort(city+1,city+5); //排序
        a[i]=city[2].dec;
        fa[i]=hash[city[2].h];
        b[i]=city[1].dec;
        fb[i]=hash[city[1].h];
        for(int j=0;j<=16;j++)
        {
            if(j==0) //一个轮回
            {
                if(fa[i])
                {
                    va[i][0]=a[i];
                    to[i][0]=fa[i];
                }
            }
            else if(j==1) //2^1=2个轮回
            {
                if(fb[fa[i]])
                {
                    va[i][1]=a[i];
                    vb[i][1]=b[fa[i]];
                    to[i][1]=fb[fa[i]];
                }
            }
            else if(to[to[i][j-1]][j-1]) //2^x,x>1,倍增来求答案
            {
                va[i][j]=va[i][j-1]+va[to[i][j-1]][j-1];
                vb[i][j]=vb[i][j-1]+vb[to[i][j-1]][j-1];
                to[i][j]=to[to[i][j-1]][j-1];
            }
            else break;
        }
    }
}

double cal1(int x,int val) //起点为x,总距离为val,得到的a开车距离/b开车距离的值
{
    int t1=0,t2=0; //t1=a走的距离,t2=b走的距离
    for(int i=16;i>=0;i--)
    {
        if(to[x][i]&&t1+va[x][i]+t2+vb[x][i]<=val)
        {
            t1+=va[x][i]; //更新a、b走过的距离
            t2+=vb[x][i];
            x=to[x][i]; //更新走到的城市
        }
    }
    if(t2==0) return INF;
    return (double)t1/(double)t2; //返回a走的距离/b走的距离的比值
}

void cal2(int x,int val) //起点为x,总距离为val,输出aheb走过的距离
{
    int t1=0,t2=0;
    for(int i=16;i>=0;i--)
        if(to[x][i]&&t1+va[x][i]+t2+vb[x][i]<=val)
        {
            t1+=va[x][i];
            t2+=vb[x][i];
            x=to[x][i];
        }
    printf("%d %d\n",t1,t2);
}

void solve1() //回答第一问
{
    double minDiv=1e60; //最小比值
    int ans; //最小比值取得的时候的起点下标
    int x0;
    scanf("%d",&x0);
    for(int i=1;i<=n;i++)
    {
        double nowDiv=cal1(i,x0);
        if(nowDiv<minDiv||(nowDiv==minDiv&&h[i]>h[ans])) //注意||后面的条件,若两个比值相同但是当前的高度高一些,则取当前的
        {
            minDiv=nowDiv;
            ans=i;
        }
    }
    printf("%d\n",ans);
}

void solve2() //回答第二问
{
    int m;
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        int s,x;
        scanf("%d%d",&s,&x);
        cal2(s,x);
    }
}

int main()
{
    scanf("%d",&n);
    bst.insert(-INF);
    bst.insert(INF);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&h[i]);
        hash[h[i]]=i; //标记该高度对应的城市编号
    }
    preWork(); //预处理
    solve1(); //回答第一问
    solve2(); //回答第二问
    return 0;


 

 

你可能感兴趣的:([Codevs 1199][NOIP 2012提高组]开车旅行)