ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

APIO 2019 桥梁

2020-04-01 17:04:30  阅读:324  来源: 互联网

标签:int ed void 桥梁 write read 2019 APIO top


题目链接

LOJ3145
LuoguP5443

题目概括

给定一张 \(N\) 个点,\(M\) 条边的无向带权图。

每次询问给定一个二元组 \((x,y)\),从 \(x\) 号节点开始出发,只允许通过边权 \(\geq y\) 的边。

问能够到达的联通块最大的大小。

要求动态修改边权

数据范围:\(N\leq 5\times 10^4,M\leq 10^5\)

思路要点

子任务讨论

暴力就不进行过多的讲解。

子任务 链

本质是一个序列问题。

你可以发现每一次的操作是进行一次向左边扩展和向右边扩展,找到第一条不满足的边。也就是说你需要找到一个包含起点的最大区间,满足在上面的所有边权都满足 \(\geq y\)。

因为支持修改操作,所以考虑用二分答案,线段树查询的方法在 \(O(nlog^2n)\) 的时间解决。

子任务 只有查询

我们假设联通块中最小的边权为 \(min\),那么某一个起点开始能够遍历整个联通块的充要条件是 \(min \ge y\)。

也就是最小边的边权 \(\ge y\),这就容易想到用 \(Kruscal\) 重构树来实现这个过程。

建立出 \(Kruscal\) 重构树之后,以每一个节点为根节点的子树,都可以被以根节点为起点的路径包含,所以只需要倍增找到最后一个满足条件的祖先节点,然后其子树大小即为答案。

也可以采用离线做法,将操作离线后,按照 \(y\) 值从大到小查询,将所有的边按照顺序加入到并查集中,答案就是联通块大小

正解

采用「子任务 只有查询」中的做法,考虑暴力。

我们按照询问分块,在当前块中的所有操作都相当于和前面所有块的到的新图重新暴力求一遍答案,然后更新为新图

将当前块中不需要修改的边加入到联通块中,考虑修改的边,将在当前操作之前的每一条边最后一次操作得到的权值和标准值 \(y\) 进行比较,否则视为未被修改。

每一次操作,同一条边可能被不同的修改操作,所以考虑要可撤销并查集

复杂度分析

笔者不会分析这一部分的复杂度,而且觉得网上大部分的分析都有一点问题。

以上复杂度分析可能存在问题

代码

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

namespace FastIO {

template <class T> 
void rd(T& x) {
  x = 0;
  char ch = 0;
  bool f = 0;
  while (!isdigit(ch)) f |= ch == '-', ch = getchar(); 
  while (isdigit(ch)) x = x * 10 + (ch ^ 48), ch = getchar();
  f ? x = -x : 1;
}

template <class T> 
void ptf(T x) {
  if (x < 0) putchar('-'), x = -x;
  if (x > 9) ptf(x / 10);
  putchar(x % 10 + 48);
}

void read(int& x) { rd(x); }
void read(long long& x) { rd(x); }
void read(unsigned int& x) { rd(x); }
void read(unsigned long long& x) { rd(x); }
void read(char& x) { x = getchar(); }
void read(string& x) { cin >> x; }
template <class T, class R> 
void read(pair<T, R>& x) {
  read(x.first), read(x.second);
}
template <class T> 
void read(vector<T>& x) { 
  for (auto& ele : x) read(x); 
}

void write(int x) { ptf(x); }
void write(long long x) { ptf(x); }
void write(unsigned long long x) { ptf(x); }
void write(unsigned int x) { ptf(x); }
void write(char x) { putchar(x); }
void write(char* x) { printf("%s", x); }
void write(string x) { cout << x; }
template <class T> 
void write(vector<T> x) {
  for (auto ele : x)  (x); 
}
template <class T, class R> 
void write(pair<T, R> x) {
  write(x.first), putchar(','), write(x.second);
}
template <class T> 
void writeln(T x) {
  write(x), puts("");
}

}

using FastIO::read;
using FastIO::write;
using FastIO::writeln;

namespace RevocableDisjoinSet {

const int N = 1e5 + 5; 

struct DisjoinSet {
  int top, n; 
  int fa[N], sz[N];
  pair<int, int> stk[N];

  void init(int x) {
    n = x, top = 0;
    for (int i = 1; i <= n; i++) {
      fa[i] = i; 
      sz[i] = 1;
    }
  }

  int get(int x) {
    return fa[x] == x ? x : get(fa[x]); 
  }

  void merge(int x, int y) {
    int p1 = get(x), p2 = get(y);
    if (p1 != p2) {
      if (sz[p1] > sz[p2]) { swap(p1, p2); }
      fa[p1] = p2; 
      sz[p2] += sz[p1];
      stk[++top] = make_pair(p1, p2);
    }
  }

  void revoke(int goal) {
    while (top > goal) {
      fa[stk[top].first] = stk[top].first;
      fa[stk[top].second] = stk[top].second; 
      sz[stk[top].second] -= sz[stk[top].first];
      top--; 
    }
  }

  int check(int x, int y) {
    return get(x) == get(y);
  }
};

}

// ===============

RevocableDisjoinSet::DisjoinSet dsu;

const int N = 5e4 + 5, M = 1e5 + 5; 

struct Ed {
  int u, v, w, id; // 现在第 i 条边 原来的编号
  
  bool operator<(const Ed& rhs) const {
    return w > rhs.w || (w == rhs.w && id < rhs.id);
  }
} ed[M], ed2[M], tmp[M];

struct Op {
  int t, x, y, id;

  bool operator<(const Op& rhs) const {
    return y > rhs.y;
  }
} op[M], op1[M], op2[M];

int n, m, q, tot;
int ismdy[M], did[M], ans[M];
int rk[M]; // 第 i 条边的 rank (按照 w 排序)

/**
 * 对于当前的边 i, 原来的编号为 ed[i].i 
 * 其中原来的编号是没有排序过的,现在的编号为排序完的下标
 */
void solve() {
  dsu.init(n);
  memset(ismdy, 0, sizeof ismdy);
  int t1 = 0, t2 = 0, pt = 1; // pt 遍历现在的编号
  for (int i = 1; i <= tot; i++) 
    if (op[i].t == 1) {
      op1[++t1] = op[i];
      ismdy[op[i].x] = 1; // 将这条边的原来的编号打上修改标记
    } else 
      op2[++t2] = op[i];
  // op2 是询问 op1 是修改
  sort(op2 + 1, op2 + 1 + t2);
  for (int i = 1; i <= t2; i++) {
    while (pt <= m && ed[pt].w >= op2[i].y) {
      if (!ismdy[ed[pt].id]) 
        dsu.merge(ed[pt].u, ed[pt].v);
      pt++; 
    }
    int temp = dsu.top;
    for (int j = 1; j <= t1; j++) 
      did[op1[j].x] = 0;
    for (int j = 1; j <= t1; j++) 
      if (op1[j].id < op2[i].id) 
        did[op1[j].x] = j; 
    for (int j = 1; j <= t1; j++) 
      if (did[op1[j].x] == j) {
        if (op1[j].y >= op2[i].y) 
          dsu.merge(ed[rk[op1[j].x]].u, ed[rk[op1[j].x]].v);
      } else if (did[op1[j].x] == 0 && ed[rk[op1[j].x]].w >= op2[i].y) 
        dsu.merge(ed[rk[op1[j].x]].u, ed[rk[op1[j].x]].v);
    ans[op2[i].id] = dsu.sz[dsu.get(op2[i].x)];
    dsu.revoke(temp);
  }
  memset(did, 0, sizeof did);
  int l = 1, r = 1, num = 0, t = 0;
  for (int i = t1; i >= 1; i--) 
    if (!did[op1[i].x]) {
      did[op1[i].x] = 1; 
      ed2[++num] = ed[rk[op1[i].x]];
      ed2[num].w = op1[i].y; 
    }
  sort(ed2 + 1, ed2 + 1 + num);
  while (l <= m && r <= num) {
    while (l <= m && did[ed[l].id]) 
      l++;
    if (ed[l].w >= ed2[r].w) 
      tmp[++t] = ed[l], l++;
    else 
      tmp[++t] = ed2[r], r++;
  }
  while (l <= m) {
    if (!did[ed[l].id])
      tmp[++t] = ed[l];
    l++;
  }
  while (r <= num) 
    tmp[++t] = ed2[r], r++;
  for (int i = 1; i <= m; i++) {
    ed[i] = tmp[i];
    rk[ed[i].id] = i; 
  }
}

int main() {
  read(n), read(m);
  for (int i = 1; i <= m; i++) {
    read(ed[i].u), read(ed[i].v), read(ed[i].w);
    ed[i].id = i; 
  }
  sort(ed + 1, ed + 1 + m);
  for (int i = 1; i <= m; i++) 
    rk[ed[i].id] = i; 
  read(q);
  tot = 0;
  for (int i = 1; i <= q; i++) {
    read(op[++tot].t), read(op[tot].x), read(op[tot].y);
    op[tot].id = i; 
    if (tot == 500) {
      solve();
      tot = 0;
    }
  }
  if (tot) 
    solve();
  for (int i = 1; i <= q; i++) 
    if (ans[i]) 
      writeln(ans[i]);
  return 0;
}

后言

  • 当操作复杂,且不容易优化的时候,可以尝试用分块来降低复杂度。
  • 还是强调一定要一步步分析问题,也要通过题目的性质来看出题目的模型。

标签:int,ed,void,桥梁,write,read,2019,APIO,top
来源: https://www.cnblogs.com/chhokmah/p/12613763.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有