NC16562 开车旅行
题目链接
关键点:
1、预处理:将所有点到达另一个点的最短和次短距离先求出
方法:利用set,从最后一个城市开始插入,每次插入到set中,查看左右是否存在有城市(即set是按照海拔高度从小到大排序的),要查看左右的旁边和再旁边是否存在城市,然后更新每个点的次短和最短距离
说明下数组:dis[i][j]表示从i点出发,j=0表示最短距离点,j=1表示次短距离点,nxt[i][j]表示从i点出发,j=0表示最短距离,j=1表示次短距离
代码中有详细的解释
for (int i=n; i>=1; i--)
{
s.insert(city[i]);
it = s.find(city[i]);//将城市进行实时按照海拔排序,方便求出左右的距离
if (it != s.begin())//若存在比当前城市海拔小的城市,更新最短次短距离
{
it--;
update(city[i], *it);
if (it != s.begin())//继续更新
{
it--;
update(city[i], *it);
it++;
}
it++;//还原it,重新指向city[i]
}
if ((++it) != s.end())//若此时存在比city[i]海拔大的城市
{
update(city[i], *it);
++it;
if (it != s.end())//存在次短
{
update(city[i], *it);
it--;
}
it--;//还原it
}
}
update函数
void update(ty t1, ty t2)
{
if (!nxt[t1.num][0])//如果t1点的最短点还没求出
{
nxt[t1.num][0] = t2.num;
dis[t1.num][0] = abs(t1.h - t2.h);
}
else if (dis[t1.num][0]>abs(t1.h-t2.h) || ((dis[t1.num][0] == abs(t1.h - t2.h)) && (city[nxt[t1.num][0]].h > t2.h)))//如果t1点到t2点的距离更短 或者 t1点到t2点的距离与t1此时的最短距离相同,但是t2点的海拔比那个最短距离点的海拔小
{
nxt[t1.num][1] = nxt[t1.num][0];//从前的最短点变成次短点
dis[t1.num][1] = dis[t1.num][0];
nxt[t1.num][0] = t2.num;//更新最短点
dis[t1.num][0] = abs(t1.h - t2.h);
}
else if (dis[t1.num][1]>abs(t1.h-t2.h) || ((dis[t1.num][1] == abs(t1.h - t2.h)) && (city[nxt[t1.num][1]].h > t2.h)))//此时的短是比次短点短,如果t1点到t2点的距离更短 或者 t1点到t2点的距离与t1此时的最短距离相同,但是t2点的海拔比那个最短距离点的海拔小
{
nxt[t1.num][1] = t2.num;//更新次短点
dis[t1.num][1] = abs(t1.h - t2.h);
}
else if (!nxt[t1.num][1])//若此时没有次短点
{
nxt[t1.num][1] = t2.num;//更新次短点
dis[t1.num][1] = abs(t1.h - t2.h);
}
}
2、然后是利用两个数组,进行倍增。g[i][j]维护从i点出发,开2^j轮走到的点(A先开)
f[i][j][0/1]维护从i点出发,走2^j次方轮,0表示A走的次短距离,1表示B走的次短距离
初始化
for (int i=1; i<=n; i++)
{
g[i][0] = nxt[nxt[i][1]][0];//从i城市出发,走一轮(A、B各走一次)到达的点(因为A先开车)
f[i][0][0] = dis[i][1];//从i点出发,走一轮,A行驶路程
f[i][0][1] = dis[nxt[i][1]][0];//从i点出发,走一轮,B行驶路程
}
然后
for (int j=1; j<=20; j++)
{
for (int i=1; i<=n; i++)
{
g[i][j] = g[g[i][j-1]][j-1];//从i点出发走2^j次方轮到达的点为 从i点出发走2^(j-1)次方轮到达的点再走2^(j-1)次方轮到达的点
f[i][j][0] = f[i][j-1][0] + f[g[i][j-1]][j-1][0];//A点从i点出发走2^j轮行驶的路程 = A点从i点出发走2^(j-1)轮走的路程 + 从2^(j-1)次方轮点走2^(j-1)次方轮A行驶的路程
f[i][j][1] = f[i][j-1][1] + f[g[i][j-1]][j-1][1];//和上面类似
}
}
3、对于询问一,给出x0,枚举每个城市,求出a行驶的路程和b行驶的路程,然后进行比值计算(比值计算可以换成乘法计算),那么这个根据出发城市s,总行驶路程x,求出a行驶的路程和b行驶的路程这个过程在询问二中也有出现,因此可以维护成一个函数
void query(int s, int x, ll &disa, ll &disb)//出发城市为s,最大行驶路程为x,a行驶的距离为disa,b行驶的距离为disb
{
for (int i=20; i>=0; i--)
{
if (f[s][i][0] + f[s][i][1] <= x && (g[s][i]))//如果两人行驶的总路程小于x,并且两人都可以到达下一个点
{
disa += f[s][i][0];//更新A、B行驶的总路程
disb += f[s][i][1];
x -= (f[s][i][0] + f[s][i][1]);//总行驶距离更新
s = g[s][i];//下一轮的出发点更新
}
}
if (nxt[s][1] && dis[s][1] <= x)//若此时只能A走一步
disa += dis[s][1];
}
完整代码(详细注释)
# include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 100000+10;
ll dis[N][2], nxt[N][2], f[N][21][2], g[N][21];//dis表示从i点出发,j=0表示最短距离点,j=1表示次短距离点,nxt表示从i点出发,j=0表示最短距离,j=1表示次短距离;f表示从i点出发,走2^j次方轮,0表示A走的次短距离,1表示B走的次短距离,g表示从i点出发,开2^j轮走到的点(A先开)
struct ty{
int h;
int num;
bool operator < (const ty a) const{
return h<a.h;
}
}city[N];//存储每个城市的海拔点
set<ty>s;
set<ty>::iterator it;
int n, m;
void update(ty t1, ty t2)
{
if (!nxt[t1.num][0])//如果t1点的最短点还没求出
{
nxt[t1.num][0] = t2.num;
dis[t1.num][0] = abs(t1.h - t2.h);
}
else if (dis[t1.num][0]>abs(t1.h-t2.h) || ((dis[t1.num][0] == abs(t1.h - t2.h)) && (city[nxt[t1.num][0]].h > t2.h)))//如果t1点到t2点的距离更短 或者 t1点到t2点的距离与t1此时的最短距离相同,但是t2点的海拔比那个最短距离点的海拔小
{
nxt[t1.num][1] = nxt[t1.num][0];//从前的最短点变成次短点
dis[t1.num][1] = dis[t1.num][0];
nxt[t1.num][0] = t2.num;//更新最短点
dis[t1.num][0] = abs(t1.h - t2.h);
}
else if (dis[t1.num][1]>abs(t1.h-t2.h) || ((dis[t1.num][1] == abs(t1.h - t2.h)) && (city[nxt[t1.num][1]].h > t2.h)))//此时的短是比次短点短,如果t1点到t2点的距离更短 或者 t1点到t2点的距离与t1此时的最短距离相同,但是t2点的海拔比那个最短距离点的海拔小
{
nxt[t1.num][1] = t2.num;//更新次短点
dis[t1.num][1] = abs(t1.h - t2.h);
}
else if (!nxt[t1.num][1])//若此时没有次短点
{
nxt[t1.num][1] = t2.num;//更新次短点
dis[t1.num][1] = abs(t1.h - t2.h);
}
}
void query(int s, int x, ll &disa, ll &disb)//出发城市为s,最大行驶路程为x,a行驶的距离为disa,b行驶的距离为disb
{
for (int i=20; i>=0; i--)
{
if (f[s][i][0] + f[s][i][1] <= x && (g[s][i]))//如果两人行驶的总路程小于x,并且两人都可以到达下一个点
{
disa += f[s][i][0];//更新A、B行驶的总路程
disb += f[s][i][1];
x -= (f[s][i][0] + f[s][i][1]);//总行驶距离更新
s = g[s][i];//下一轮的出发点更新
}
}
if (nxt[s][1] && dis[s][1] <= x)//若此时只能A走一步
disa += dis[s][1];
}
int main()
{
scanf("%d", &n);
for (int i=1; i<=n; i++)
{
scanf("%d", &city[i].h);
city[i].num = i;
}
for (int i=n; i>=1; i--)
{
s.insert(city[i]);
it = s.find(city[i]);//将城市进行实时按照海拔排序,方便求出左右的距离
if (it != s.begin())//若存在比当前城市海拔小的城市,更新最短次短距离
{
it--;
update(city[i], *it);
if (it != s.begin())//继续更新
{
it--;
update(city[i], *it);
it++;
}
it++;//还原it,重新指向city[i]
}
if ((++it) != s.end())//若此时存在比city[i]海拔大的城市
{
update(city[i], *it);
++it;
if (it != s.end())//存在次短
{
update(city[i], *it);
it--;
}
it--;//还原it
}
}
for (int i=1; i<=n; i++)
{
g[i][0] = nxt[nxt[i][1]][0];//从i城市出发,走一轮(A、B各走一次)到达的点(因为A先开车)
f[i][0][0] = dis[i][1];//从i点出发,走一轮,A行驶路程
f[i][0][1] = dis[nxt[i][1]][0];//从i点出发,走一轮,B行驶路程
}
for (int j=1; j<=20; j++)
{
for (int i=1; i<=n; i++)
{
g[i][j] = g[g[i][j-1]][j-1];//从i点出发走2^j次方轮到达的点为 从i点出发走2^(j-1)次方轮到达的点再走2^(j-1)次方轮到达的点
f[i][j][0] = f[i][j-1][0] + f[g[i][j-1]][j-1][0];//A点从i点出发走2^j轮行驶的路程 = A点从i点出发走2^(j-1)轮走的路程 + 从2^(j-1)次方轮点走2^(j-1)次方轮A行驶的路程
f[i][j][1] = f[i][j-1][1] + f[g[i][j-1]][j-1][1];//和上面类似
}
}
int x0;
scanf("%d", &x0);
int s0 = 0;
ll a = 1e15, b = 0;
for (int i=1; i<=n; i++)//枚举每个出发城市
{
ll disa = 0, disb = 0;
query(i, x0, disa, disb);
if (disb && (!s0 || disa*b < disb*a))//若disb不为0(若为0,比值为无穷大),且disa/disb会小(比值比较换成分母分子相乘比较,先初始化a/b = 无穷大,则满足disa/disb < a/b 即disa*b < disb*a)
{
s0 = i;//更新最小比值城市
a = disa;//更新比值
b = disb;
}
}
cout<<s0<<endl;
scanf("%d", &m);
for (int i=1; i<=m; i++)
{
int s, x;
ll disa = 0, disb = 0;
scanf("%d%d", &s, &x);
query(s, x, disa, disb);
cout<<disa<<" "<<disb<<endl;
}
return 0;
}