[NOIP2012]开车旅行

P1081 [NOIP2012 提高组] 开车旅行

题目大意

旅行过程中有个n个城市,从西向东按序号排列。

旅行过程中,小 \(\text{A}\) 和小 \(\text{B}\) 轮流开车,第一天小 \(\text{A}\) 开车,之后每天轮换一次。他们计划选择一个城市 \(s\) 作为起点,一直向东行驶,并且最多行驶 \(x\) 公里就结束旅行。

\(\text{A}\) 和小 \(\text{B}\) 的驾驶风格不同,小 \(\text{B}\) 总是沿着前进方向选择一个最近的城市作为目的地,而小 \(\text{A}\) 总是沿着前进方向选择第二近的城市作为目的地(注意:本题中如果当前城市到两个城市的距离相同,则认为离海拔低的那个城市更近)。如果其中任何一人无法按照自己的原则选择目的城市,或者到达目的地会使行驶的总距离超出 \(x\) 公里,他们就会结束旅行。

需要回答两个问题。

1、 对于一个给定的 \(x=x_0\),从哪一个城市出发,小 \(\text{A}\) 开车行驶的路程总数与小 \(\text{B}\) 行驶的路程总数的比值最小(如果小 \(\text{B}\) 的行驶路程为 \(0\),此时的比值可视为无穷大,且两个无穷大视为相等)。如果从多个城市出发,小 \(\text{A}\) 开车行驶的路程总数与小 \(\text{B}\) 行驶的路程总数的比值都最小,则输出海拔最高的那个城市。

2、对任意给定的 \(x=x_i\) 和出发城市 \(s_i\),小 \(\text{A}\) 开车行驶的路程总数以及小 \(\text B\) 行驶的路程总数。

分析

本题是明显的实现不算特别麻烦,但是思路特别难想到。我尽量将完整的思路推导过程叙述清楚。

预处理

首先,我们可以发现一个特点,若是城市已经选定,那接下里对于小A和小B而言,接下来去的点已经确定了。

对于编号为x的点,所求的就是编号x+1~n的城市构成的集合中距离城市x最近和次近的城市

这个操作,我们之前也遇见过很多,可以直接用C++中的set实现,动态的维护关于最大值最小值或某个值附近的值。

这里就不多说了。

其中有几点需要注意。

  • 由于查找时会涉及到x周围的四个节点,因此在一开始要先向平衡树分别插入2个极大值和极小值节点,以免越界。其中要注意,极大值极小值设置后,其计算过程中很容易就爆int了,因此一定一定开long long,并且题解中排名第一的虽然没开long long也过了,但是是因为样例太弱了,而且设置的最大值恰好可以过样例,但我们一定要注意。
  • 由于在平衡树中每个城市需要保存编号和海拔两个数据,因此在使用set时要重载运算符来保证节点set中的顺序。
  • set的迭代器只支持++和--两个操作,不能使用其它运算。
struct City
{
	int id;
    LL al;//identifier,altitude
	friend bool operator < (City a,City b)
    {
        return a.al<b.al; 
    }
};

multiset<City> q;

void pre()
{
    h[0]=INF,h[n+1]=-INF;
    City st;
    st.id = 0,st.al = INF;
    q.insert(st),q.insert(st);
    st.id = n + 1,st.al = -INF;
    q.insert(st),q.insert(st);
    for(int i=n;i;i--)
    {
        int ga,gb;
        City now;
        now.id = i,now.al = h[i];
        q.insert(now);
        auto p = q.lower_bound(now);
        p--;
        LL lt = (*p).id,lh = (*p).al;
        p++,p++;
        LL ne = (*p).id,nh = (*p).al;
        p--;
        if(abs(nh-h[i])>=abs(h[i]-lh))
        {
            gb = lt;
            p--;p--;
            if(abs(nh-h[i])>=abs(h[i]-(*p).al)) ga = (*p).id;
            else ga = ne;
        }
        else 
        {
            gb = ne;
            p++,p++;
            if(abs(h[i]-lh)<=abs((*p).al-h[i])) ga = lt;
            else ga = (*p).id;
        }
    }
}

倍增优化

接下来,我们预处理完后,知道了所有点分别由小A和小B驾驶时会去到的城市。

那我们重新来看一下问题,现在的问题变成了。

  1. 我们在只能走x路程的情况下,我们从哪个城市开始,能使得小A走的路程与小B走的路程的比值最小
  2. 我们只能走x路程的情况,且起点固定的情况下,小A和小B分别走的路程

我们发现,第一个问题我们可以遍历所有的点,这样就变成了第二个问题。

则我们现在只用解决第二个问题即可。

我们只能走x路程的情况,且起点固定的情况下,小A和小B分别走的路程

这里用到的倍增思想是非常常用的优化。LCA,ST表与多重背包等都用了这样的思路优化。

我们可以发现,这与求LCA的时候非常像。

每个点走固定步数能到的终点和能走的距离都知道了,那我们可以预处理出来,每个点走\(2^i\)的步数可以到的终点与距离。这样我们就可以用二进制拼凑的思想,去得到最接近x的答案。

这里推荐一个例题。P1613 跑路

好啦,现在我们知道我们要计算什么啦。

  • \(f_{i,j,k}表示从城市j出发,行驶2^i天,k先开车,最终会到达的城市\)
  • \(da_{i,j,k}表示从城市j出发,行驶2^i天,k先开车,小A行驶的路程长度\)
  • \(db_{i,j,k}表示从城市j出发,行驶2^i天,k先开车,小B行驶的路程长度\)

\(其中k=0表示小A先开车,k=1表示小B先开车\)

那我们的预处理推导也比较好想啦,其中需要注意的一个是。从\(2^0转移到2^1时,前后两半开车的人会发生变化\)

初值

  • \(f_{0,j,0}=ga_j,f_{0,j,1}=gb_j\)
  • \(da_{0,j,0}=dist_{j,ga_j},da_{0,j,1}=0\)
  • \(db_{0,j,0}=0,db_{0,j,1}=dist_{j,gb_j}\)
for(int i=n;i;i--)
{
	//......预处理
    f[0][i][0] = ga,f[0][i][1] = gb;
    da[0][i][0] = abs(h[i] - h[ga]);
    db[0][i][1] = abs(h[i] - h[gb]);
}

递推式子

i=1时:

  • \(f_{1,j,k}=f_{0,f_{0,j,k},1-k}\)
  • \(da_{1,j,k}=da_{0,j,k}+da_{0,f_{0,j,k},1-k}\)
  • \(db_{1,j,k}=db_{0,j,k}+db_{0,f_{0,j,k},1-k}\)

i>1时:

  • $ f_{i,j,k}=f_{i-1,f_{i-1,j,k},k}$
  • \(da_{i,j,k}=da_{i-1,j,k}+da_{i-1,f_{i-1,j,k},k}\)
  • \(db_{i,j,k}=db_{i-1,j,k}+db_{i-1,f_{i-1,j,k},k}\)
for(int i=1;i<=18;i++)
    for(int j=1;j<=n;j++)
        for(int k=0;k<2;k++){
            if(i==1)
            {
                auto anc = f[0][j][k];
                f[1][j][k] = f[0][anc][1-k];
                da[1][j][k] = da[0][j][k] + da[0][anc][1-k];
                db[1][j][k] = db[0][j][k] + db[0][anc][1-k];
            }
            else 
            {   
                auto anc = f[i-1][j][k];
                f[i][j][k] = f[i-1][anc][k];
                da[i][j][k] = da[i-1][j][k] + da[i-1][anc][k];
                db[i][j][k] = db[i-1][j][k] + db[i-1][anc][k];
            }
        }

解决问题1

刚刚,我们就说了,解决问题1就是遍历每个点,当成问题2解决。

int la,lb,ansid;
  
void calc(int S,int X)
{
	int p=S;
	la=0,lb=0;//初始化
	for(int i=18;i>=0;i--)
		if(la+lb+da[i][p][0]+db[i][p][0]<=X)
		{
			la+=da[i][p][0];
			lb+=db[i][p][0];
			p=f[i][p][0];
		}//倍增模拟前进
}
                                                          
for(int i=1;i<=n;i++)
{
	calc(i,x0);
	double nowans=(double)la/(double)lb;//注意精度问题
	if(nowans<ans)
	{
		ans=nowans;
		ansid=i;
	}
	else
		if(nowans==ans && h[ansid]<h[i])
			ansid=i;//比较求解
}
cout<<ansid<<endl;

解决问题2

就是对于每个给定的\(x_i,s_i\),用一次\(calc(s_i,x_i)\)

Ac_code

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
const LL INF = 5e9;

struct City
{
	int id;
    LL al;//identifier,altitude
	friend bool operator < (City a,City b)
    {
        return a.al<b.al; 
    }
};

int n,m,x0,la,lb,ansid;
int s[N],x[N];
int f[20][N][2];
LL da[20][N][2],db[20][N][2],h[N];
double ans = INF*1.0;
multiset<City> q;

void calc(int S,int X)
{
    int p = S;
    la = 0,lb = 0;
    for(int i=18;i>=0;i--){
        if(la+lb+da[i][p][0]+db[i][p][0]<=X)
        {
            la += da[i][p][0];
            lb += db[i][p][0];
            p = f[i][p][0];
        }
    }
}

void pre()
{
    h[0]=INF,h[n+1]=-INF;
    City st;
    st.id = 0,st.al = INF;
    q.insert(st),q.insert(st);
    st.id = n + 1,st.al = -INF;
    q.insert(st),q.insert(st);
    for(int i=n;i;i--)
    {
        int ga,gb;
        City now;
        now.id = i,now.al = h[i];
        q.insert(now);
        auto p = q.lower_bound(now);
        p--;
        LL lt = (*p).id,lh = (*p).al;
        p++,p++;
        LL ne = (*p).id,nh = (*p).al;
        p--;
        if(abs(nh-h[i])>=abs(h[i]-lh))
        {
            gb = lt;
            p--;p--;
            if(abs(nh-h[i])>=abs(h[i]-(*p).al)) ga = (*p).id;
            else ga = ne;
        }
        else 
        {
            gb = ne;
            p++,p++;
            if(abs(h[i]-lh)<=abs((*p).al-h[i])) ga = lt;
            else ga = (*p).id;
        }
        f[0][i][0] = ga,f[0][i][1] = gb;
        da[0][i][0] = abs(h[i] - h[ga]);
        db[0][i][1] = abs(h[i] - h[gb]);
    }
    for(int i=1;i<=18;i++)
        for(int j=1;j<=n;j++)
            for(int k=0;k<2;k++){
                if(i==1)
                {
                    auto anc = f[0][j][k];
                    f[1][j][k] = f[0][anc][1-k];
                    da[1][j][k] = da[0][j][k] + da[0][anc][1-k];
                    db[1][j][k] = db[0][j][k] + db[0][anc][1-k];
                }
                else 
                {   
                    auto anc = f[i-1][j][k];
                    f[i][j][k] = f[i-1][anc][k];
                    da[i][j][k] = da[i-1][j][k] + da[i-1][anc][k];
                    db[i][j][k] = db[i-1][j][k] + db[i-1][anc][k];
                }
            }
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%lld",h+i);
    scanf("%d%d",&x0,&m);
    for(int i=1;i<=m;i++) scanf("%d%d",s+i,x+i);
    pre();
    for(int i=1;i<=n;i++)
    {
        calc(i,x0);
        double nans = (double)la/(double)lb;
        if(nans<ans)
        {
            ans = nans;
            ansid = i;
        }
        else if(nans==ans&&h[ansid]<h[i])
            ansid = i;
    }
    printf("%d\n",ansid);
    for(int i=1;i<=m;i++)
    {
        calc(s[i],x[i]);
        printf("%d %d\n",la,lb);
    }
    return 0;
}
posted @ 2022-07-01 17:37  艾特玖  阅读(345)  评论(0)    收藏  举报