1. 程式人生 > 其它 >[題解] HDU 3038 How Many Answers Are Wrong

[題解] HDU 3038 How Many Answers Are Wrong

[題解] HDU 3038 How Many Answers Are Wrong

·題目大意

有一段長度為 \(n\) 的序列 \(S\),現在給定一共 \(m\) 條資訊。
每條資訊格式為 \(l\) \(r\) \(v\)
表示 \(sum(l,r)=v\) 。顯然,這 \(m\) 條資訊中有一些是與之前矛盾的,
現在請你輸出一共有多少條矛盾的資訊。
\(1\leq n \leq 2 \times 10^5\)\(1\leq m \leq 4 \times 10^4\), 有多組資料

·解題思路

看到這種題目,第一眼想到的就是差分約束,但是首先圖要支援動態維護邊,並且單次時間複雜度為 \(O(nm)\)

,顯然不可行。
既然不能用差分約束,那麼我們就想到了用帶權並查集來維護資訊。
假設我們用 \(fa[x]\) 來表示 \(x\) 的父節點,陣列 \(val[x]\) 來表示節點 \(x\) 到根節點的權值,並且在合併時固定使得 \(fa[x] = y\)
首先,我們要把閉區間變為開區間 \([l,r]\) --> \([l,r+1)\)
那麼當我們處理一條資訊 \(l\) , \(r\) , \(v\) 時,有兩種情況:

  • 在同一個並查集合,這時如果不矛盾,
    則有 \(v + val[r] = val[l]\)
    所以只需判斷 \(v == val[l] - val[r]\)
    即可。
  • 不在同一個並查集,我們要把它們合併,並把被合併的根節點更新,
    這時 \(l\) 到新的根節點的距離 \(d\) 有兩種方法表示:
    \(d = v + val[r]\)\(d = val[l] + val[find(l)]\)
    其中 \(find(l)\) 為更新前 \(l\) 的根節點。
    那麼只需更新 \(val[find(l)] = v + val[r] - val[l]\) 即可。
  • 單次時間複雜度為 \(O(n \times \alpha(n))\)

·程式碼實現

#include <iostream>
#define reg register
using namespace std;
namespace io {
template<typename T>inline void read(T &x) {
  char ch, f = 0; x = 0;
  while (!isdigit(ch = getchar())) f |= ch == '-';
  while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
  x = f? -x: x;
}
char ch[20];
template<typename T>inline void write(T x) {
  (x < 0) && (x = -x, putchar('-'));
  x || putchar('0');
  char i = 0;
  while (x) ch[i++] = x % 10 ^ 48, x /= 10;
  while (i) putchar(ch[--i]);
}
}
#define rd io::read
#define wt io::write
const int maxN = 200010;
int fa[maxN], val[maxN];
int n, m, ans;
int find(int);
bool merge(int, int, int);
int main() {
  while(~scanf("%d%d", &n, &m)) {
    ans = 0;
    for (reg int i = 1; i <= n + 1; ++i) fa[i] = i, val[i] = 0;
    for (reg int i = 1, x, y, z; i <= m; ++i) {
      rd(x); rd(y); rd(z);
      ans += merge(x, y + 1, z);
    }
    wt(ans); putchar('\n');
  }
  return 0;
}
int find(int x) {
  if (x == fa[x]) return x;
  int k = fa[x];
  fa[x] = find(fa[x]); val[x] += val[k]; //更新權值並路徑壓縮
  return fa[x];
}
bool merge(int x,int y,int v) {
  int a = find(x), b = find(y);
  if (a ^ b) { //不在同一集合中合併並更新,其中 a ^ b 相當於 a != b
    fa[a] = b;
    val[a] = v + val[y] - val[x];
    return false;
    //v + val[y] == val[a] + val[x]
  }
  return v ^ val[x] - val[y]; //在同一集合中判斷
}