首页 > 其他分享 >P2934 [USACO09JAN] Safe Travel G 题解

P2934 [USACO09JAN] Safe Travel G 题解

时间:2024-10-22 20:43:25浏览次数:6  
标签:int 题解 void USACO09JAN maxN siz dfn P2934 dis

一个用平衡树,不用脑子的写法。(目前没有用平衡树的诶。)


题意

不经过最短路的最后一条边的最短路,保证最短路唯一。

思路

看到最短路唯一容易想到建出的最短路 DAG 其实是最短路树(以 \(1\) 为根)。

那题意转化为求每个节点不经过与父亲的连边,所能到根节点的最短路。

容易发现每个点的答案都是被子树内的点与子树外的点的连边贡献的,称这些边为关键边。

记节点 \(u,v\) 之间边的边权是 \(w(u,v)\)。节点 \(u\) 到 \(1\) 的最短路为 \(dis_u\)。

比如:

节点 \(2\) 的答案是 \(\min(w(2,4)+w(4,7)+dis_7,w(2,5)+w(5,6)+dis_6)\)。红边为关键边。

在最短路树中 dfs 求解。

用平衡树来维护关键边的信息。

具体的,维护经过这条边的到当前节点的路径长度,以及这条边连接的子树外的节点的 dfn 序。

平衡树内的点按记录的 dfn 序有序排列。

子节点的信息合并到当前节点,暴力合并即可,具体可以看这个这个

但要注意多了一条边,需要给这颗子树内所有路径的长度加上这条边的权值。

还需要解决一个问题,当我们计算节点 \(8\) 的答案时,那两条关键边失效了,需要删除。
这时刚才记录的这些边连接的点的 dfn 序就有用了。
这些边的终点都是当前子树内的点(起点不用说,所有边的起点都在子树内),根据这个直接删除即可。

平衡树实现了全局最小值,全局加,带交合并。

复杂度的瓶颈在于平衡树的带交合并。

代码

#include <bits/stdc++.h>

using namespace std;

int read() {
  int s = 0, w = 1;
  char c = getchar();
  while (!isdigit(c)) {
    w = c == '-' ? -w : w;
    c = getchar();
  }
  while (isdigit(c)) {
    s = s * 10 + c - 48;
    c = getchar();
  }
  return s * w;
}

const int inf = 1e9;
const int maxN = 1e5 + 7;

int n, m;

int head[maxN], tot;
struct edge {
  int to, ls, w;
} e[maxN * 4];
void add(int u, int v, int w) {
  e[++tot] = {v, head[u], w};
  head[u] = tot;
}

int dis[maxN], pre[maxN], prw[maxN];
bool vis[maxN];
using pii = pair<int, int>;
void dij() {
  fill(dis + 1, dis + n + 1, inf);
  dis[1] = 0;
  priority_queue<pii, vector<pii>, greater<pii>> Q;
  Q.emplace(0, 1);
  while (!Q.empty()) {
    int f = Q.top().second;
    Q.pop();
    if (vis[f]) continue;
    vis[f] = true;
    for (int i = head[f]; i; i = e[i].ls) {
      int to = e[i].to, w = e[i].w;
      if (dis[to] > dis[f] + w) {
        pre[to] = f, prw[to] = w;
        dis[to] = dis[f] + w;
        Q.emplace(dis[to], to);
      }
    }
  }
}

struct wei {
  int to, w;
  wei(int to, int w) : to(to), w(w) {}
  friend bool operator < (wei A, wei B) {
    return A.to < B.to;
  }
};
vector<wei> E[maxN];

int dfn[maxN], cnt, siz[maxN];
void initdfs(int x) {
  dfn[x] = ++cnt;
  siz[x] = 1;
  for (auto [to, w] : E[x])
    initdfs(to), siz[x] += siz[to];
}

mt19937 rd(5222568);
int nwn, root[maxN];
struct tree {
  int l, r;
  int to, w;
  int key;
  int siz;
  int mn;
  int tg;
} t[maxN * 4];
int New(int to, int v) {
  int p = ++nwn;
  t[p].l = t[p].r = t[p].tg = 0;
  t[p].siz = 1;
  t[p].to = to;
  t[p].mn = t[p].w = v;
  t[p].key = rd();
  return p;
}
void upd(int p) {
  t[p].siz = t[t[p].l].siz + t[t[p].r].siz + 1;
  t[p].mn = min({t[t[p].l].mn, t[t[p].r].mn, t[p].w});
}
void make(int p, int v) {
  if (!p) return;
  t[p].mn += v;
  t[p].w += v;
  t[p].tg += v;
}
void down(int p) {
  if (!t[p].tg) return;
  make(t[p].l, t[p].tg);
  make(t[p].r, t[p].tg);
  t[p].tg = 0;
}
int merge(int x, int y) {
  if (!x || !y) return x | y;
  if (t[x].key <= t[y].key) {
    down(x);
    t[x].r = merge(t[x].r, y);
    upd(x);
    return x;
  } else {
    down(y);
    t[y].l = merge(x, t[y].l);
    upd(y);
    return y;
  }
}
void split(int p, int k, int &x, int &y) {
  if (!p) x = y = 0;
  else {
    down(p);
    if (t[p].to <= k)
      x = p, split(t[p].r, k, t[x].r, y);
    else
      y = p, split(t[p].l, k, x, t[y].l);
    upd(p);
  }
}
int M(int x, int y) {
  if (!x || !y) return x | y;
  if (t[x].key > t[y].key) swap(x, y);
  int L, R;
  down(x), down(y);
  split(y, t[x].to, L, R);
  t[x].l = M(t[x].l, L);
  t[x].r = M(t[x].r, R);
  upd(x);
  return x;
}
void insert(int &p, int to, int v) {
  int L, R;
  split(p, to, L, R);
  p = merge(merge(L, New(to, v)), R);
}
void del(int &p, int x) {
  int L, M, R;
  split(p, dfn[x] + siz[x] - 1, L, R);
  split(L, dfn[x] - 1, L, M);
  p = merge(L, R);
}

int ans[maxN];
void dfs(int x) {
  for (auto [to, w] : E[x]) {
    dfs(to);
    make(root[to], w);
    root[x] = M(root[x], root[to]);
  }
  for (int i = head[x]; i; i = e[i].ls) {
    int to = e[i].to, w = e[i].w;

    if (dfn[x] <= dfn[to] && dfn[to] <= dfn[x] + siz[x] - 1)
      continue;
    if (to == pre[x] && w == prw[x]) continue;

    insert(root[x], dfn[to], w + dis[to]);
  }
  del(root[x], x);

  ans[x] = root[x] ? t[root[x]].mn : -1;
}

signed main() {
//  freopen("in.in", "r", stdin);
//  ofstream cout("out.out");

  t[0].mn = inf;

  n = read(), m = read();
  for (int i = 1; i <= m; i++) {
    int u = read(), v = read(), w = read();
    add(u, v, w), add(v, u, w);
  }
  dij();

  for (int i = 1; i <= n; i++)
    if (pre[i])
      E[pre[i]].emplace_back(i, prw[i]);

  initdfs(1);

  dfs(1);

  for (int i = 2; i <= n; i++)
    cout << ans[i] << '\n';
}

标签:int,题解,void,USACO09JAN,maxN,siz,dfn,P2934,dis
From: https://www.cnblogs.com/ccxswl/p/18493701

相关文章

  • 【NOIP2021】方差 题解
    前言题目链接:洛谷;LOJ;UOJ。题意简述给你单调不降序列\(\{a_n\}\),你可以让\(a_i\getsa_{i-1}+a_{i+1}-a_i\),求操作后方差的最小值。\(n\leq10^4\),\(1\leqa_i\leq600\)。题目分析仔细观察操作,发现实际上是将\(a_i\)按照\(a_{i-1}\)和\(a_{i+1}\)的......
  • 题解:P10977 Cut the Sequence
    题目传送门分析看到这种题就可以想到动态规划,先设状态:$f_i$表示考虑前$i$个数,所需要的最小代价。发现$f_i$可以从所有$i$以前的状态加后一段区间转移过来,于是可以列出状态转移方程:$$f_i=\min_{j=i-1}^{s_i-s_j\leqm}(f_j+\max_{k=j+1}^i)$$其中$j$......
  • [题解]P2671 [NOIP2015 普及组] 求和
    P2671[NOIP2015普及组]求和可以发现我们对相同颜色且编号奇偶性相同的元素归为一组,组内的元素两两都满足题目条件,且这样可以不重不漏覆盖所有答案。设分完组之后,某一组内的元素编号分别是\(a_1,a_2,\dots,a_q\),数字分别是\(b_1,b_2,\dots,b_q\),则根据题意,该组的答案是:\[\lar......
  • 20241021 校测T1 致敬传奇捆绑测试题目(Perm) 题解
    题解:致敬传奇捆绑测试题目Perm来自不知道什么时候的回忆。给定正整数\(n\),一个\(1\simn\)的排列\(p\)是一个好排列,当且仅当使得对于任意\(1\lek<n\),都有\(\sum_{i=1}^kp_i>p_{k+1}\)。现在请你求出字典序第小的好排列\(p\)。\(1\len\le10^6\),\(1\lek\le......
  • [ARC133E] Cyclic Medians 题解
    一点不会套路。思路对于中位数相关,发现我们不好直接表示与中位数有关的内容。不妨枚举\(x\),把大于\(x\)的标为\(1\),小于等于\(x\)的标为\(0\),这样把所有最终为一的方案数加起来就是原来的答案。大概是这样一个东西:\[k=\sum_{i=0}^k[i<k]\]这个怎么求呢。发现若一组......
  • Public NOIP Round #7 T3 黑白棋子 题解
    Description有一棵\(n\)个点的树,顶点的编号为\(1\)到\(n\)。对于树中的每个顶点,可能存在一个白色的棋子、一个黑色的棋子,或者没有棋子。树上正好有\(w\)个白色棋子和\(b\)个黑色棋子。另外,对于每一对具有相同颜色棋子的顶点,存在一条路径,路径上的每个顶点都包含相同颜色......
  • AT_agc064_c [AGC064C] Erase and Divide Game 题解
    先考虑所有\(l_i=r_i\)时怎么做,可以建出反向Trie树,问题转化为从根开始每次向左子树或右子树走,第一个拿到空子树的人输,直接在Trie上dp即可。考虑从叶子层开始对每一层的点合并两个子树的dp值,发现每一层值相同的连续段是较少的。于是可以维护这些连续段,每次合并要将每个......
  • 洛谷P2596 [ZJOI2006] 书架 题解 splay tree 模板题
    题目链接:https://www.luogu.com.cn/problem/P2596主要涉及的操作就是:找到某一个编号的点(这个操作可以不用splaytree维护)删除某个点将某一个点插入到最前面,最后面,或者某一个位置查询前序遍历为\(k\)的节点编号因为每次删除都会又把这个点加回去,所以可以复用\(n\)个......
  • [题解]CF825E Minimal Labels
    LPhang为什么是神?思路显然可以想到一个错误的贪心:直接拓扑排序,每一次选择当前可以拓展的点中最小的元素进行编号。由于可能存在一个值较小的元素被藏在一个较大的元素后面,这种贪心就会出问题。出问题的本质原因就是我们希望字典序最小,就得使得越小的位置分配到更小的值。不妨......
  • 历年CSP-J初赛真题解析 | 2017年CSP-J初赛完善程序(27-36)
    学习C++从娃娃抓起!记录下CSP-J备考学习过程中的题目,记录每一个瞬间。附上汇总贴:历年CSP-J初赛真题解析|汇总_热爱编程的通信人的博客-CSDN博客(快速幂)请完善下面的程序,该程序使用分治法求x......