2021牛客暑期多校训练营4 E.Tree Xor 线段树+扫描线原理

Posted kaka0010

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2021牛客暑期多校训练营4 E.Tree Xor 线段树+扫描线原理相关的知识,希望对你有一定的参考价值。

原题链接:https://ac.nowcoder.com/acm/contest/11255/E

题意

有一棵树,每个节点都有一个权值 a [ i ] a[i] a[i]的取值范围 [ L [ i ] , R [ i ] ] [L[i],R[i]] [L[i],R[i]],每条边也有权值 w w w满足 a [ u ] ⨁ a [ v ] a[u] \\bigoplus a[v] a[u]a[v],询问方案数。

分析

我们知道如果确定一个点,那么所有点基本上就已经确定了。但枚举一个点的所有权值的话, O ( 2 30 ∗ n ) O(2^{30}*n) O(230n)复杂度肯定是不行的。

但如果我们先确定一个点为基准点,那么它与所有点的异或值都可以处理出来的,因此我们求的方案数其实就是所有点 [ L [ i ] , R [ i ] ] ⨁ a ′ [ i ] ] [L[i],R[i]]\\bigoplus a'[i]] [L[i],R[i]]a[i]]集合取并集。还有一个问题,就是这个异或之后的区间并不是连续的,但不会超过log个区间(可以自己手算一下)。因此我们需要构造一些区间使得每次异或之后的答案是连续的,类似 [ x x x 0000 , x x x 1111 ] [xxx0000, xxx1111] [xxx0000,xxx1111]的区间(前缀相同)不论异或上任何值,最后的区间一定是连续的,例如 [ 0 , 3 ] ⨁ 6 = [ 4 , 7 ] [0,3]\\bigoplus6=[4,7] [0,3]6=[4,7]很快我们发现这不就是权值线段树上节点的区间吗?

最后只要把所有区间扫一遍,利用扫描线的思想,当前值为n就统计区间长度。
这是题解中 n l o g n 2 nlogn^2 nlogn2的做法,有些偷懒了,当然还可以做到 n l o g n nlogn nlogn,只要在线段树上打一下懒惰标记,做一下区间加最后统计和为n的个数也是可以的。

Code

#include <bits/stdc++.h>
#define lowbit(i) i & -i
#define Debug(x) cout << x << endl
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair<ll, ll> PII;
const ll INF = 1e18;
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int MOD = 998244353;
int n, L[N], R[N];
struct Edge {
    int to, next, w;
}e[N<<1];
int cnt, h[N], tot, rt, ls[N*40], rs[N*40], sum[N*40], tag[N*40];
vector<PII> ve;
void add(int u, int v, int w) {
    e[cnt].to = v;
    e[cnt].w = w;
    e[cnt].next = h[u];
    h[u] = cnt++;
}
void insert(int &now, int l, int r, int ql, int qr, int dep, int val) {
    if (!now) now = ++tot;
    if (ql <= l && qr >= r) {
        int Left = (val ^ l) >> dep << dep;
        int Right = Left + (1 << dep) - 1;
        ve.push_back({Left, 1});
        ve.push_back({Right+1, -1});
        return;
    }
    int mid = (l + r) >> 1;
    if (ql <= mid) insert(ls[now], l, mid, ql, qr, dep-1, val);
    if (qr > mid) insert(rs[now], mid+1, r, ql, qr, dep-1, val);
}
void dfs(int x, int fa, int val) {
    for (int i = h[x]; ~i; i = e[i].next) {
        int v = e[i].to;
        if (v == fa) continue;
        insert(rt, 0, (1<<30)-1, L[v], R[v], 30, val ^ e[i].w);
        dfs(v, x, val ^ e[i].w);
    }
}
void solve() {
    cin >> n;
    memset(h, -1, sizeof h);
    for (int i = 1; i <= n; i++) cin >> L[i] >> R[i];
    for (int i = 1; i <= n-1; i++) {
        int u, v, w; cin >> u >> v >> w;
        add(u, v, w), add(v, u, w);
    }
    dfs(1, 0, 0);
    ve.push_back({L[1], 1});
    ve.push_back({R[1] + 1, -1});
    sort(ve.begin(), ve.end());
    int sum = 0;
    int ans = 0;
    for (int i = 0; i < ve.size(); i++) {
        sum += ve[i].se;
        if (sum == n && i + 1 < ve.size()) {
            ans += ve[i+1].fi - ve[i].fi;
        }
    }
    cout << ans << endl;
}
signed main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
#ifdef ACM_LOCAL
    freopen("input", "r", stdin);
    freopen("output", "w", stdout);
    signed test_index_for_debug = 1;
    char acm_local_for_debug = 0;
    do {
        if (acm_local_for_debug == '$') exit(0);
        if (test_index_for_debug > 20)
            throw runtime_error("Check the stdin!!!");
        auto start_clock_for_debug = clock();
        solve();
        auto end_clock_for_debug = clock();
        cout << "Test " << test_index_for_debug << " successful" << endl;
        cerr << "Test " << test_index_for_debug++ << " Run Time: "
             << double(end_clock_for_debug - start_clock_for_debug) / CLOCKS_PER_SEC << "s" << endl;
        cout << "--------------------------------------------------" << endl;
    } while (cin >> acm_local_for_debug && cin.putback(acm_local_for_debug));
#else
    solve();
#endif
    return 0;
}

以上是关于2021牛客暑期多校训练营4 E.Tree Xor 线段树+扫描线原理的主要内容,如果未能解决你的问题,请参考以下文章

2021牛客暑期多校训练营4 Tree Xor (区间异或上一个数+区间求交)

2021牛客暑期多校训练营4 Tree Xor (区间异或上一个数+区间求交)

2021牛客暑期多校训练营4

2021牛客暑期多校训练营 4 题解

2021牛客暑期多校训练营4,签到题CFIJ

2021牛客暑期多校训练营2