树上差分

树上差分

Global Positioning System

题意:

通过m次测量,测量出n个点之间的大小关系,(三维的大小关系),然后有可能一个点测量出错了,问你有哪些边修改之后能够保证这些点之间的大小关系相同。

如果 1 -> 2 (1,1,1), 1->3(2,2,2) 如果2->3 不等于 1,1,1 那么这三个点中有一个是错的,急需要修改。
所以结果需要输出 
3
1 2 3

思路:

如何判断一个点是不是树边

如果一个点走到了一个走过同时deep比自己小的点,那么这个点一定是一个非树边。

非矛盾

然后如果这个点的两次 相对距离如果相等的话,那么这个环就不能动,因为他已经是一个没有矛盾的环了,你动任意一条边都会让这个环的矛盾。如果当前点 是 u ,deep 比较小的那个点是 j ,相当于把 uj这个链上的点都锁住,不准动,我们就把 u>j上的点都+1,表示他们被锁住了

矛盾

然后如果这个点的两次 相对距离如果不相等的话,那么这个环就必须要动,因如果当前点 是 u ,deep 比较小的那个点是 j ,相当于把 uj这个链上的点都打上标记,我们就把 u>j上的点都+1,表示他们被标记了,之后需要改动这些边中的一条从而满足条件。

https://s1.ax1x.com/2022/08/19/vrIgRf.png

快速处理一条链上问题

用树上差分 将deep小的-1,把deep 大的那个点+1,然后递归回去的时候累加,就可以达到将一条链中的除了开始的那个点以外其他的点加1的操作。

Code

#include <algorithm>
#include <bitset>
#include <cmath>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <vector>

#define int long long
#define x first
#define y second
#define ps push_back
#define endl '\n'

#define kd                     \
  ios::sync_with_stdio(false); \
  cin.tie();                   \
  cout.tie(0);
using namespace std;
typedef pair<int, int> pi;
const int N = 2e6 + 100, mod = 1e9 + 7, INF = 1e10;
int lowbit(int x) { return x & -x; }
int gcd(int a, int b) { return a % b == 0 ? b : gcd(b, a % b); }

struct T {
  int v, a, b, c, id;
  // T to_pair()
  T operator+(const T x) const {
    return {v, a + x.a, b + x.b, c + x.c, id + x.id};
      // 说一下为啥v和id 也要+ 
      /*
      可以不加但是必须要有人把这个坑填上,因为返回的是一个结构体,如果你前面没有填上的话。那么他会默认把你的 前面几个看成0 也就是 如果你当前是 {1,a1,b1,c1,id1}+{2,a2,b2,c3,id2} 
      如果你写成 {a + x.a, b + x.b, c + x.c, id + x.id}; 那么新的v3=a+x.a  a3=b+x.b, b3=c1+x.c c3=0;
      
      */
  }
  bool operator==(const T x) const { return a == x.a && b == x.b && c == x.c; }
};

vector<T> v[N];
int de[N], st[N], s1[N], s2[N];
// de 代表深度,用来判断是不是非树边,st 看有没有走过
// s1 s2 分别代表两种类型的权值,矛盾和非矛盾
/*
最后的结果,肯定是一个一定能动的点也就是 =没有被锁定过也就是 s1=0的点。
同时要s2最大,代表修改这个点能够将所有的有矛盾的环改对,
*/
T cur[N];// 每个点第一次的相对距离
int ex;
/*
ex 的作用比较抽象,建议画个图: 如果一个环上每个点都能被修改,同时 s2的最大值是1,说明有矛盾的环只有一个,那么我们如果直接枚举 s1=0 s2=1的情况会漏掉 那个非树边,所以我们用ex记录非树边,特别判断一下这个情况。
*/
int id[N];
//每个点对应的边的编号
// 当前点、 当前的权值 、 边的编号、上一个点
void dfs(int u, T nw, int fid, int pr) {
  de[u] = de[pr] + 1;//...
  cur[u] = nw;//u的第一次出现的权值就是 
  id[u] = fid;// 点对应的边的编号
  st[u] = 1;// 出现过
  for (int i = 0; i < v[u].size(); i++) {
    int j = v[u][i].v, id = v[u][i].id;
    if (j == pr) continue;
    // cout << j << " " << u << endl;
    if (!st[j]) {//如果没出现过先递归下去
      T nw2 = nw + v[u][i];// 相对距离要加
      dfs(j, nw2, id, u);//递归下去
        //累加
      s1[u] += s1[j];
      s2[u] += s2[j];
    } else if (de[j] < de[u]) {
      T nw2 = nw + v[u][i], nw3 = cur[j];
      if (nw2 == nw3) {//对应两种情况
        s1[j]--;
        s1[u]++;
      } else {
        s2[j]--;
        s2[u]++;
        ex = v[u][i].id;// 可能会用到特判 只有mx只有1的情况
      }
    }
  }
}

void solve() {
  int n, m;
  cin >> n >> m;
  for (int i = 1; i <= m; i++) {
    int a, b;
    int x, y, z;
    cin >> a >> b >> x >> y >> z;
    v[a].ps({b, x, y, z, i});
    v[b].ps({a, -x, -y, -z, i});
  }
  T cc = {0, 0, 0, 0, 0};
  dfs(1, cc, 0, 0);
  int mx = 0;
  for (int i = 1; i <= n; i++) mx = max(mx, s2[i]);//最后取出需要修改环的最大值
  vector<int> ans;
  for (int i = 2; i <= n; i++) {
    if (s2[i] == mx && !s1[i]) {// 上面说过了!
        //只有一个点 能够满足最大的矛盾环数同时他自己 是能动的情况下。
      ans.ps(id[i]);
    }
  }

  if (mx == 1) {//也说过了
    ans.ps(ex);
  }
  cout << ans.size() << endl;
  sort(ans.begin(), ans.end());
  for (int i = 0; i < ans.size(); i++) {
    cout << ans[i] << " ";
  }
  cout << endl;
}

signed main() {
  kd;
  int _;
  _ = 1;
  // cin>>_;
  while (_--) solve();
  return 0;
}
posted @   黄小轩  阅读(29)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示