Description
【NOIP2012】开车旅行
(题面太长不想描述……)
Solution
由于题目很复杂,所以我们将这道题分成三个部分分析
主要思路:dp+倍增+链表+离散化
Step1:预处理出这两个人从每一个城市出发的下一个城市
将这一步的答案记为$ga[i],gb[i]$
城市高度差=城市间的距离
考虑这两个人每一次需要选择离当前城市最近或者是第二近的城市作为目标,那么我们将所有城市的高度排序并且离散化,建立链表维护一下当前节点距离最小值和次小值,并且将当前节点删除。具体实现细节请参见李煜东《算法竞赛进阶指南》
Step2:预处理出从任意城市经过任意天最终到达的城市以及这两个人分别的路程
思考一个dp
定义$f[i][j][k]$表示当前从城市j出发,经过$2^i$天,并且一开始是k开车,所到达的终点是多少。
当$i=0$时,显然答案就是$ga[i]$或者是$gb[i]$
其他情况下,$f[i][j][k]=f[i-1][f[i-1][j][k]][k]$,特别地,当$i=1$时,状态转移方程最后一个k应当为k^1
同样考虑两个人行驶的路程,定义$disa/disb[i][j][k]$表示这两个人从城市j出发,经过$2^i$天,并且一开始是k开车,行驶的路程是多少。
转移类似于f
$disa[i][j][k]=disa[i-1][j][k]+disa[i-1][f[i-1][j][k]][k]$
disb类似
Step3:更新答案
本问题有两个子问题,但是归根结底就是要求我们计算从某一个城市出发并且总里程不超过某一个值时,这两个人分别行驶了多少
那么我们就利用disa和disb数组拼凑一下,逆序枚举一下i,尽可能多地行驶即可
最终我们就得到了正解,时间复杂度$O(n\log n)$
Code
#include// check if it is judged online namespace shl { typedef long long ll; inline ll read() { ll ret = 0, op = 1; char c = getchar(); while (!isdigit(c)) { if (c == '-') op = -1; c = getchar(); } while (isdigit(c)) { ret = ret * 10 + c - '0'; c = getchar(); } return ret * op; } const int N = 1e5 + 10; const int M = 21; int n, m; struct city { ll h; int id, l, r; bool operator <(const city &x) const { return h < x.h; } } a[N]; int pos[N], ga[N], gb[N], f[M][N][2]; ll disa[M][N][2], disb[M][N][2], ans1, ans2, x0; double minn = 1e17; inline int left(int now, int l, int r) { if (!l) return 0; if (!r) return 1; return a[now].h - a[l].h <= a[r].h - a[now].h; } inline int cmp(int now, int x, int y) { if (!x) return a[y].id; if (!y) return a[x].id; if (a[now].h - a[x].h <= a[y].h - a[now].h) return a[x].id; return a[y].id; } inline int dist(int x, int y) { return abs(a[pos[x]].h - a[pos[y]].h); } inline void find(int x, ll dis) { ans1 = ans2 = 0; int op = 0; for (register int i = 20; i >= 0; --i) if (f[i][x][op] && ans1 + ans2 + disa[i][x][op] + disb[i][x][op] <= dis) { ans1 += disa[i][x][op]; ans2 += disb[i][x][op]; if (i == 0) op ^= 1; x = f[i][x][op]; } return ; } int main() { n = read(); for (register int i = 1; i <= n; ++i) { a[i].h = read(); a[i].id = i; } std::sort(a + 1, a + n + 1); for (register int i = 1; i <= n; ++i) { a[i].l = i - 1; a[i].r = i + 1; pos[a[i].id] = i; // pos[i] biaoshi chengshi i duiyingde weizhi shiduoshao; } a[1].l = a[n].r = 0; for (register int i = 1; i <= n; ++i) { int now = pos[i]; int l = a[now].l, r = a[now].r; if (left(now, l, r)) ga[i] = cmp(now, a[l].l, r), gb[i] = a[l].id; else ga[i] = cmp(now, l, a[r].r), gb[i] = a[r].id; if (l) a[l].r = r; if (r) a[r].l = l; } for (register int i = 1; i <= n; ++i) { // 0 means the driver is A, 1 means B; if (ga[i]) f[0][i][0] = ga[i], disa[0][i][0] = dist(i, ga[i]); if (gb[i]) f[0][i][1] = gb[i], disb[0][i][1] = dist(i, gb[i]); disa[0][i][1] = disb[0][i][0] = 0; } for (register int i = 1; i <= 20; ++i) for (register int j = 1; j <= n; ++j) for (register int k = 0; k < 2; ++k) { int op = k; if (i == 1) op ^= 1; if (f[i - 1][j][k]) f[i][j][k] = f[i - 1][f[i - 1][j][k]][op]; if (f[i][j][k]) { disa[i][j][k] = disa[i - 1][j][k] + disa[i - 1][f[i - 1][j][k]][op]; disb[i][j][k] = disb[i - 1][j][k] + disb[i - 1][f[i - 1][j][k]][op]; } } x0 = read(), m = read(); int ans = 0; for (register int i = 1; i <= n; ++i) { find(i, x0); if (ans2 && ans1 / (ans2 * 1.0) < minn) { minn = ans1 / (ans2 * 1.0); ans = i; } } printf("%d\n", ans); for (register int i = 1; i <= m; ++i) { ll s = read(), x = read(); find(s, x); printf("%lld %lld\n", ans1, ans2); } return 0; } } int main() { #ifdef LOCAL freopen("textname.in", "r", stdin); freopen("textname.out", "w", stdout); #endif shl::main(); return 0; }