题意:给你一个有向图,第一类边是从第i个点到第i+1个点的,还有多出来的m条二类边,是从u到v的,同样是有向的。然后你要处理询问,从u到v经过最多一次二类边的最短距离是多少。
题目我觉得是神题,然后看了网上的一些神解法,其中一个非常neatly,在此说下感想。
首先我们考虑的是(u<v)的所有询问,那么我们可以发现,这个时候所有二类边(u>v)的,是不可能用到的。然后我们想,从u到v的最短距离,就是利用u+1~v中的某个起点的二类边到达某个点k(u<k<=v),所以在计算的时候我们只需要知道这些边里能节省的距离的最小值就可以了。所以当我们把询问离线,我们优先处理的是u大v小的边,然后对于每天边,我们可以更新一下对应的节省了的距离的最小值,然后询问的时候就是两点间的距离+节省距离的最小值。
同样,在处理(u>v)的询问的时候,所有的二类边(u<v)也是不会用到的。然后在询问(v,u)的时候,我们也是要用到的所以在u之后为起点的边,以及以u为起点,终点在v前的边。但是这个时候询问就有点坑爹了,因为我们是从u到达u1,经过一条u1->v1的边,然后从v1到达v,区间顺序如下:(v1,v,u,u1),那么花费究竟什么时候最少呢?我们如何利用u1->v1这条边去更新呢?不难发现,我们实际上的距离其实就是 dis(u1,v1)+cost(u1,v1)-dis(v,u),所以在询问的时候u,v是不起作用的,相当于一个常数,我们需要的只是dis(u1,v1)+cost(u1,v1)最小,所以更新的时候只需要维护这个就好了。至此圆满结束。
值得注意的是bit数组的维护以及更新的顺序。
这道题叫我做我肯定做不会,但是这种离线的思想我觉得太重要了,很多看上去解决不了的数据结构归结起来就是没有合适的离线处理。
#pragma warning(disable:4996) #include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<cmath> #define ll long long #define maxn 100000 #define maxm 200000 #define lowbit(k) k&(-k) using namespace std; struct Node { int u,v; ll d; int idx; int kind; bool operator < (const Node &b) const{ if (u == b.u){ if (v == b.v) return kind < b.kind; else return v < b.v; } else return u > b.u; } }seg[maxm*2]; int top; ll d[maxn + 50]; ll sd[maxn + 50]; ll ans[maxm + 50]; ll n, m; ll bit[maxn + 50]; void upd(int x, ll val){ while (x <= n){ bit[x] = min(bit[x], val); x += lowbit(x); } } ll query(int x){ ll res = (1LL) << 60; while (x){ res = min(res, bit[x]); x -= lowbit(x); } return res; } int main() { while (cin >> n >> m){ for (int i = 1; i <= n - 1; i++){ scanf("%lld", &d[i]); } sd[1] = 0; for (int i = 1; i <= n - 1; i++){ sd[i + 1] = sd[i] + d[i]; } int ui, vi; ll qi; top = 0; for (int i = 0; i < m; i++){ scanf("%d%d%lld", &seg[top].u, &seg[top].v, &seg[top].d); seg[top].idx = i; seg[top].kind = 0; top++; } int q; cin >> q; for (int i = 0; i < q; i++){ scanf("%d%d", &seg[top].u, &seg[top].v); seg[top].kind = 1; seg[top].idx = i; top++; } sort(seg, seg + top); memset(ans, 0, sizeof(ans)); memset(bit, 0, sizeof(bit)); for (int i = 0; i < top; i++){ if (seg[i].kind == 0 && seg[i].u < seg[i].v){ upd(seg[i].v, seg[i].d - (sd[seg[i].v] - sd[seg[i].u])); // 节省距离的相反数 } if (seg[i].kind == 1 && seg[i].u < seg[i].v){ ans[seg[i].idx] = query(seg[i].v) + sd[seg[i].v] - sd[seg[i].u]; } } for (int i = 1; i <= n; i++) bit[i] = (1LL << 60); for (int i = 0; i < top; i++){ if (seg[i].kind == 0 && seg[i].u>seg[i].v){ upd(seg[i].v, seg[i].d + sd[seg[i].u] - sd[seg[i].v]); } if (seg[i].kind == 1 && seg[i].u>seg[i].v){ ans[seg[i].idx] = query(seg[i].v) - (sd[seg[i].u] - sd[seg[i].v]); } } for (int i = 0; i < q; i++){ printf("%lld\n", ans[i]); } } return 0; }