• Trie 的一类应用


    \(\text{Trie}\)

    先从 [十二省联考 \(2019\)] 异或粽子 谈起
    不难想到堆加可持久化 \(Trie\) 的做法
    这就和 \(\text{[NOI2010]}\) 超级钢琴 类似了
    这种做法与 \(k\) 有关,并且很容易推及许多关于 \(\text{kth}\) 的问题
    \(O(k \log n)\)

    然而就此问题而言,还有与 \(k\) 无关的做法
    思路还算好想
    先求出 \(kth\) 的值 \(val\)
    这是第一步,对应 \(\text{CF1055F Tree and XOR}\)
    \(O(n \log V)\)

    $\text{Code}$
    #include <cstdio>
    #include <iostream> 
    #define RE register
    #define IN inline
    typedef long long LL;
    using namespace std;
    
    const int N = 1e6 + 5;
    int n, p1[N], p2[N], tr[N][2], siz[N];
    LL k, a[N], ans;
    
    IN LL read()
    {
    	LL x = 0; char ch = getchar(); int f = 1;
    	for(; !isdigit(ch); f = (ch == '-' ? -1 : f), ch = getchar());
    	for(; isdigit(ch); x = (x<<3)+(x<<1)+(ch^48), ch = getchar());
    	return x * f;
    }
    
    int main()
    {
    	n = read(), k = read(), p1[1] = p2[1] = 1; LL y;
    	for(RE int i = 2, x; i <= n; i++)
    		x = read(), y = read(), a[i] = a[x] ^ y, p1[i] = p2[i] = 1;
    	for(RE int i = 61; i >= 0; i--)
    	{
    		for(RE int j = 1; j <= n + 1; j++) tr[j][0] = tr[j][1] = siz[j] = 0;
    		int tot = 1, x = 0; LL sum = 0;
    		for(RE int j = 1; j <= n; j++)
    		{
    			int c = (a[j] >> i) & 1;
    			if (!tr[p1[j]][c]) tr[p1[j]][c] = ++tot;
    			++siz[p1[j] = tr[p1[j]][c]];
    		}
    		for(RE int j = 1; j <= n; j++) sum += siz[tr[p2[j]][(a[j] >> i) & 1]];
    		if (sum < k) x = 1, k -= sum, ans |= (1LL << i);
    		for(RE int j = 1; j <= n; j++) p2[j] = tr[p2[j]][(a[j] >> i) & 1 ^ x];
    	}
    	printf("%lld\n", ans);
    }
    

    然后在 \(Trie\) 上跑,统计当 \(val\) 某一位为 \(0\)
    各个 \(a_i\) 与其它数的异或值有多少在这一位为 \(1\) 从而大于 \(val\)
    统计这些异或值的和

    使这一位为 \(1\) 的数在 \(Trie\) 上位于一棵子树中
    统计异或值的和当然可以分位算,这要只要记录子树中某一位为 \(0,1\) 的数的数量
    可以开成与 \(Trie\) 结点数相当并附加两维大小为位数与 \(01\) 来统计数量
    然而更好的做法是:
    注意到 \(Trie\) 中同一子树中的数是按大小连续的
    可以先将 \(a\) 排序,前缀和统计每位为 \(01\) 的数量
    空间开销更小

    这个做法对应:\(\text{CF241B Friends}\)
    \(O(n\log V+n\log^2 V)\)

    $\text{Code}$
    #include <cstdio>
    #include <algorithm>
    #define IN inline
    typedef long long LL;
    using namespace std;
    
    const int P = 1e9 + 7, N = 50003, inv2 = 5e8 + 4;
    int n, size = 1;
    int tr[N * 31][2], siz[N * 31], a[N], tmp[N], num[N][31][2], L[N * 31], R[N * 31];
    LL m;
    IN void Add(LL &x, LL y) {if ((x += y - P) < 0) x += P;}
    
    IN void insert(int id, int x) {
    	int u = 1;
    	for(int i = 30; i >= 0; i--) {
    		int c = (x >> i) & 1;
    		if (!tr[u][c]) tr[u][c] = ++size, L[size] = id;
    		u = tr[u][c], ++siz[u], R[u] = id;
    	}
    }
    IN int query(LL &k) {
    	for(int i = 1; i <= n; i++) tmp[i] = 1;
    	int res = 0;
    	for(int j = 30; j >= 0; j--) {
    		LL sum = 0; int x = 0;
    		for(int i = 1; i <= n; i++) sum += siz[tr[tmp[i]][(a[i] >> j) & 1 ^ 1]];
    		if (sum >= k) res |= (1 << j), x = 1; else k -= sum;
    		for(int i = 1; i <= n; i++) tmp[i] = tr[tmp[i]][(a[i] >> j) & 1 ^ x];
    	}
    	return res;
    }
    IN void solve(int kth) {
    	for(int i = 1; i <= n; i++) tmp[i] = 1;
    	LL res = m * kth % P;
    	for(int j = 30; j >= 0; j--) {
    		int kb = ((kth >> j) & 1);
    		if (!kb)
    			for(int i = 1; i <= n; i++) {
    				int c = tr[tmp[i]][(a[i] >> j) & 1 ^ 1];
    				for(int k = 0, z; k <= 30; k++)
    					if(L[c]) Add(res, (1LL << k) * (num[R[c]][k][z = ((a[i] >> k) & 1 ^ 1)] - num[L[c] - 1][k][z]) % P);
    			}
    		for(int i = 1; i <= n; i++) tmp[i] = tr[tmp[i]][(a[i] >> j) & 1 ^ kb];
    	}
    	printf("%lld\n", res * inv2 % P);
    }
    
    int main() {
    	scanf("%d%lld", &n, &m), m <<= 1;
    	for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    	sort(a + 1, a + n + 1);
    	for(int i = 1; i <= n; i++) insert(i, a[i]);
    	for(int i = 1; i <= n; i++)
    		for(int j = 0; j <= 30; j++){
    			for(int k = 0; k < 2; k++) num[i][j][k] = num[i - 1][j][k];
    			++num[i][j][a[i] >> j & 1];
    		}
    	solve(query(m));
    }
    

    对于 异或粽子 这题我采用了上述做法(原因是堆加可持久化 \(Trie\) 调不出来。。。

    $\text{Code}$
    #include <cstdio>
    #include <algorithm>
    #include <iostream>
    #define IN inline
    typedef long long LL;
    using namespace std;
    
    const int N = 500003, LG = 32;
    int n, size = 1;
    int tr[N * 33][2], siz[N * 33], tmp[N], num[N][33][2], L[N * 33], R[N * 33];
    LL a[N], m;
    
    IN void insert(int id, LL x) {
    	int u = 1;
    	for(int i = LG; i >= 0; i--) {
    		int c = (x >> i) & 1;
    		if (!tr[u][c]) tr[u][c] = ++size, L[size] = id;
    		u = tr[u][c], ++siz[u], R[u] = id;
    	}
    }
    IN LL query(LL &k) {
    	for(int i = 1; i <= n; i++) tmp[i] = 1;
    	LL res = 0;
    	for(int j = LG; j >= 0; j--) {
    		LL sum = 0; int x = 0;
    		for(int i = 1; i <= n; i++) sum += siz[tr[tmp[i]][(a[i] >> j) & 1 ^ 1]];
    		if (sum >= k) res |= (1LL << j), x = 1; else k -= sum;
    		for(int i = 1; i <= n; i++) tmp[i] = tr[tmp[i]][(a[i] >> j) & 1 ^ x];
    	}
    	return res;
    }
    IN void solve(LL kth) {
    	for(int i = 1; i <= n; i++) tmp[i] = 1;
    	LL res = m * kth;
    	for(int j = LG; j >= 0; j--) {
    		int kb = ((kth >> j) & 1);
    		if (!kb)
    			for(int i = 1; i <= n; i++) {
    				int c = tr[tmp[i]][(a[i] >> j) & 1 ^ 1];
    				for(int k = 0, z; k <= LG; k++)
    					if (L[c]) res += (1LL << k) * (num[R[c]][k][z = ((a[i] >> k) & 1 ^ 1)] - num[L[c] - 1][k][z]);
    			}
    		for(int i = 1; i <= n; i++) tmp[i] = tr[tmp[i]][(a[i] >> j) & 1 ^ kb];
    	}
    	printf("%lld\n", res / 2);
    }
    
    IN void read(LL &x) {
    	x = 0; int f = 1; char ch = getchar();
    	for(; !isdigit(ch); f = (ch == '-' ? -1 : f), ch = getchar());
    	for(; isdigit(ch); x = (x<<3)+(x<<1)+(ch^48), ch = getchar());
    	x *= f;
    }
    
    int main() {
    	scanf("%d", &n), read(m), m <<= 1, ++n;
    	for(int i = 2; i <= n; i++) read(a[i]), a[i] ^= a[i - 1];
    	sort(a + 1, a + n + 1);
    	for(int i = 1; i <= n; i++) insert(i, a[i]);
    	for(int i = 1; i <= n; i++)
    		for(int j = 0; j <= LG; j++){
    			for(int k = 0; k < 2; k++) num[i][j][k] = num[i - 1][j][k];
    			++num[i][j][a[i] >> j & 1];
    		}
    	solve(query(m));
    }
    

    当然堆的做法是很好的,超级钢琴 这题就很好用
    不过要把可持久化 \(Trie\) 换成主席树(按理说这并不符合本文标题

    $\text{Code}$
    #include <cstdio>
    #include <queue>
    #include <iostream>
    #define IN inline
    #define RE register
    using namespace std;
    typedef long long LL;
    
    const int N = 5e5 + 5, M = N * 33;
    int n, k, L, R, s[N], Mn, Mx, rt[N];
    struct node{
    	int x, k, s;
    	IN bool operator < (const node &a) const {return s < a.s;}
    };
    priority_queue<node> Q;
    
    struct ChairmanTree{
    	int size, ls[M], rs[M], siz[M];
    	IN void insert(int &x, int y, int v, int l, int r)
    	{
    		siz[x = ++size] = siz[y] + 1, ls[x] = ls[y], rs[x] = rs[y];
    		if (l == r) return;
    		int mid = l + r >> 1;
    		if (v <= mid) insert(ls[x], ls[y], v, l, mid);
    		else insert(rs[x], rs[y], v, mid + 1, r);
    	}
    	IN int Query(int x, int y, int k, int l, int r)
    	{
    		if (l == r) return l;
    		int mid = l + r >> 1, s;
    		if ((s = siz[ls[x]] - siz[ls[y]]) >= k) return Query(ls[x], ls[y], k, l, mid);
    		return Query(rs[x], rs[y], k - s, mid + 1, r);
    	}
    }T;
    
    IN void read(int &x)
    {
    	x = 0; int f = 1; char ch = getchar();
    	for(; !isdigit(ch); f = (ch == '-' ? -1 : f), ch = getchar());
    	for(; isdigit(ch); x = (x<<3)+(x<<1)+(ch^48), ch = getchar());
    	x *= f;
    }
    
    IN node Query(int x, int y)
    {
    	int u = rt[x - L], v = rt[max(x - R - 1, 0)];
    	if (T.siz[u] - T.siz[v] < y) return node{0, 0, 0};
    	return node{x, y, s[x] - T.Query(u, v, y, Mn, Mx)};
    }
    
    int main()
    {
    	read(n), read(k), read(L), read(R), Mn = 1e9, Mx = -1e9;
    	for(RE int i = 2; i <= n + 1; i++) read(s[i]), s[i] += s[i - 1], Mn = min(Mn, s[i]), Mx = max(Mx, s[i]);
    	T.insert(rt[1], 0, 0, Mn, Mx);
    	for(RE int i = 2; i <= n + 1; i++) T.insert(rt[i], rt[i - 1], s[i], Mn, Mx);
    	for(RE int i = L + 1; i <= n + 1; i++) Q.push(Query(i, 1));
    	LL ans = 0;
    	for(; k; k--)
    	{
    		node now = Q.top(); Q.pop(), ans += now.s, now = Query(now.x, now.k + 1);
    		if (now.x) Q.push(now);
    	}
    	printf("%lld\n", ans);
    }
    

    而在测试时发现神奇的东西:
    \(\text{O2}\) ,若有未定义行为(如访问未初始化内存),则会出错
    而数组越界,有时会变得无影响,有时会公正地让你 \(WA\)
    所以初始化,数组边界要非常重视
    自然溢出可以用,其他类型的溢出要慎重

  • 相关阅读:
    Mysql:用户操作命令
    Cookie和Set-Cookie
    Matcher中appendReplacement()方法与replaceAll()方法的联系
    记录一次mysql执行异常(磁盘空间)
    ehcache简单配置
    md5
    mysql后台运行简单的备份脚本
    shell脚本研习
    SpringBoot根据条件,去注入需要的Bean
    springboot后台跨域设置
  • 原文地址:https://www.cnblogs.com/leiyuanze/p/16486175.html
Copyright © 2020-2023  润新知