【计蒜客】危险路径(dfs+并查集+tag)

危险路径

Description

给定一个 n 个点 m 条边的连通无向图,其中点从 1 到 n 标号,而每条边有一个危险值。

对于任意一条路径,定义路径上危险值的最大值为这条路径的危险值。

对于任意不同的两点 u 和 v,定义 d(u, v) 为所有从 u 到 v 的路径的危险值最小值。

对于每个点 u,定义 ,表示点 u 的危险程度。

你的任务就是计算每个点的危险程度。

为了便于输出,你只需要给出的值即可,其中 代表按位异或。

Input

第一行包含一个正整数 T,表示有 T 组测试数据。

接下来依次描述 T 组测试数据。对于每组测试数据:

第一行包含两个正整数 n 和 m。

接下来 m 行,每行描述一条无向边,包含三个正整数 u, v 和 w,表示有一条连接点 u 与点 v 的无向边,其危险值是 w。

注意,任意两点可能直接连有多条边,而且边的两个端点可能相同。

保证

保证所有测试数据的 n 之和不超过 ,m 之和不超过

Output

对于每组测试数据,输出一行信息"Case #x: y"(不含引号),其中x表示这是第x组测试数据,y表示这组测试数据需要给出的值。

Sample Input

  1
  5 10
  1 5 10
  1 4 6
  1 3 4
  5 5 0
  1 1 1
  2 1 3
  4 3 2
  5 1 5
  5 2 9
  3 5 7

Sample Output

Case #1: 69

Hint

对于第一组样例,有 f(1) = f(2) = 16, f(3) = f(4) = 15, f(5) = 20。

Solution

两点之间的危险值为所有路径值中最小的,首先考虑,对于直接相连的两点,如果它们之间相连的边也是最小的,那么显然这条边就是要选择的边;对于任意两点,优先寻找经过边权值都比较小的路径;因此,可以考虑,先尽量利用小的边,然后再加入稍大的边。

将边排升序,依次选择,已经相连的点是一个集合,每次新加入一条边,因为是升序加入的,所以当前图中所有通过这条边相通的两点间的危险值都是这条边的权值。该边连接的两个集合A,B,加入此边时,A中所有点都能连接到B,反之亦然。设集合大小分别为 ,此边权为w,则A中所有点的f值都要加上 ,B中所有点的f值都要加上

合并时类似并查集,将相连的点放入一个集合,但每次都加一遍值就需要把当前集合的所有元素存起来(MLE)。可以考虑用tag数组将需要加的值存起来,最后一起处理。两个集合合并时,以这两个集合为叶子结点建一个二叉树,新建相同的根节点。依次向上建树,并标记tag数组。最后从root节点向下dfs把所有tag一次加到对应节点上。

注意:会爆 long long 要用 unsigned long long ,ull的范围是[0,2^64-1], ll的范围是[-2^63,2^63-1]

Code

  #include 
  #include 
  #include 
  #include 
  #define maxn 1000006
  #define INF 0x3f3f3f3f
  typedef unsigned long long ll;
  
  using namespace std;
  
  int n, m, tot, tot2, node;
  int fa[maxn], head[maxn], siz[maxn];
  ll d[maxn], tag[maxn];
  struct Edge
  {
      int u, v, w;
  }edge[maxn];
  struct Tree
  {
      int to, next;
  }tree[maxn];
  void add(int u, int v, int w){edge[tot].u = u; edge[tot].v = v; edge[tot++].w = w;}
  bool cmp(Edge a, Edge b){return a.w < b.w;}
  void build(int u, int v){tree[tot2].to = v; tree[tot2].next = head[u]; head[u] = tot2++;}
  void Init()
  {
      tot = tot2 = 0; node = n;
      memset(head, -1, sizeof(head));
      memset(tag, 0, sizeof(tag));
      for (int i = 1; i <= n; i++){
          fa[i] = i; d[i] = 0; siz[i] = 1; 
      }
  }
  int Find(int x)
  {
      if (fa[x] == x) return x;
      return fa[x] = Find(fa[x]);
  }
  void Union(int x, int y, int root)
  {
      x = Find(x); y = Find(y);
      if (x == y) return;
      fa[x] = fa[y] = root; fa[root] = root;
      siz[root] = siz[x] + siz[y];
  }
  bool Same(int x, int y)
  {
      return Find(x) == Find(y);
  }
  void dfs(int tmp, ll sum)
  {
      for (int i = head[tmp]; ~i; i = tree[i].next){
          int to = tree[i].to;
          if (to <= n)
              d[to] += sum + tag[to];
          dfs(to, sum + tag[to]);
      }
  }
  int main()
  {
      int T, cas, u, v, w;
  
   //   freopen("in.txt", "r", stdin);
      scanf("%d", &T);
      for (int cas = 1; cas <= T; cas++){
          scanf("%d%d", &n, &m);
          Init();
          for (int i = 1; i <= m; i++){
              scanf("%d%d%d", &u, &v, &w);
              add(u, v, w);
          }
          sort(edge, edge + tot, cmp);
          for (int i = 0; i < tot; i++){
              u = edge[i].u; v = edge[i].v; w = edge[i].w;
              if (Same(u, v)) continue;
              int fu = Find(u), fv = Find(v);
              tag[fu] = siz[fv] * 1ll * w; tag[fv] = siz[fu] * 1ll * w;
              node++; 
              build(node, fu); build(node, fv);
              Union(u, v, node);
          }
          dfs(node, tag[node]);
          ll ans = 0;
          for (int i = 1; i <= n; i++){
              ans ^= (i * 1ll * d[i]);
          }
          printf("Case #%d: %llu\n", cas, ans);
      }
      return 0;
  }

你可能感兴趣的:(并查集,dfs)