• 2020CCPC长春 F. Strange Memory(树上启发式合并)


    Once there was a rooted tree. The tree contained n nodes, which were numbered 1,…,n. The node numbered 1 was the root of the tree. Besides, every node i was assigned a number a**i. Your were surprised to find that there were several pairs of nodes (i,j) satisfying a**ia**j=alca(i,j),

    where ⊕ denotes the bitwise XOR operation, and lca(i,j) is the lowest common ancestor of i and j, or formally, the lowest (i.e. deepest) node that has both i and j as descendants.

    Unfortunately, you cannot remember all such pairs, and only remember the sum of ij for all different pairs of nodes (i,j) satisfying the above property. Note that (i,j) and (j,i) are considered the same here. In other words, you will only be able to recall ni=1nj=i+1a**ia**j=alca(i,j).

    You are assumed to calculate it now in order to memorize it better in the future.

    首先暴力做法就是双重遍历点求LCA然后判断,复杂度显然爆炸,考虑如何优化。既然枚举点不可,那么可以考虑枚举LCA,求LCA为根的子树对于答案的贡献,加起来就是最终的答案了。这样就自然想到了dsu on tree。由于异或的性质,可知要满足的条件\(a[x]\ xor\ a[y] = a[LCA]\)等价于\(a[y]=a[LCA]\ xor\ a[x]\),那么可以维护一个vector数组vector<int> vec[N],vec[i]存储值为i的点的下标。那么在遍历LCA的时候先暴力搜轻儿子,再搜重儿子且保留信息(存在vec数组),再依次搜轻儿子,对于每个轻儿子为根的子树先更新答案再更新vec数组(这样保证产生贡献的两个点的最近公共祖先一定是枚举到的LCA)。但这样的话每次需要扫一遍vector,复杂度也是无法接受的。注意到异或运算的话每一位都是独立的,可以考虑分别计算每一位的贡献然后加起来。设cnt[i, j, k]表示值为i,下标第j位为k的数的个数。只有两个下标第j位不相同,这一位才对答案有贡献,因此获取k的时候需要用x的第j位和1异或。

    #include <iostream>
    #include <cstring>
    #define ll long long
    const int N = 1e5 + 5;
    using namespace std;
    int n, a[N], head[N], ver[2 * N], Next[2 * N], tot = 0, sz[N], son[N];
    long long ans = 0;
    int hson = 0;
    int cnt[N * 15][21][2];//第一维一定要开够
    void add(int x, int y) {
    	ver[++tot] = y, Next[tot] = head[x], head[x] = tot;
    }
    void dfs1(int x, int pre) {
    	sz[x] = 1;
    	int mxsz = -1;
    	for(int i = head[x]; i; i = Next[i]) {
    		int y = ver[i];
    		if(y == pre) continue;
    		dfs1(y, x);
    		sz[x] += sz[y];
    		if(sz[y] > mxsz) {
    			son[x] = y;
    			mxsz = sz[y];
    		}
    	}
    }
    void update(int x, int pre, int lca) {
    	int tmp = a[x] ^ a[lca];
    	for(int i = 0; i <= 20; i++) {
    		ans += (1ll << i) * (cnt[tmp][i][!((x >> i) & 1)]);
    	}
    	for(int i = head[x]; i; i = Next[i]) {
    		int y = ver[i];
    		if(y == pre || y == hson) continue;
    		update(y, x, lca);
    	}
    }
    void modify(int x, int pre, int v) {
    	for(int i = 0; i <= 20; i++) {
    		cnt[a[x]][i][(x >> i) & 1] += v;//x的信息必须在这里添加才不会产生影响
    	}
    	for(int i = head[x]; i; i = Next[i]) {
    		int y = ver[i];
    		if(y == pre || y == hson) continue;
    		modify(y, x, v);
    	}
    }
    void dfs2(int x, int pre, bool keep) {
    	for(int i = head[x]; i; i = Next[i]) {
    		int y = ver[i];
    		if(y == pre || y == son[x]) continue;
    		dfs2(y, x, 0);
    	}
    	if(son[x]) {
    		dfs2(son[x], x, 1);
    		hson = son[x];
    	}
    	for(int i = head[x]; i; i = Next[i]) {//统计以a[x]为lca的答案
    		int y = ver[i];
    		if(y == pre || y == son[x]) continue;//因为重儿子已经add过一遍了,不能再添加了
    		//必须先计算再添加 否则可能出现v在u到假设的lca的链上,这样假设的lca就不是真正的lca了
    		update(y, x, x);
    		modify(y, x, 1);
    	}
    	for(int i = 0; i <= 20; i++) {
    		cnt[a[x]][i][(x >> i) & 1]++;//x的信息必须在这里添加才不会产生影响
    	}
    	hson = 0;
    	if(!keep) {
    		modify(x, pre, -1);
    	}
    }
    signed main() { 
    	cin >> n;
    	memset(cnt, 0, sizeof(cnt));
    	for(int i = 1; i <= n; i++) {
    		cin >> a[i];
    	}
    	for(int i = 1; i <= n - 1; i++) {
    		int u, v;
    		cin >> u >> v;
    		add(u, v);
    		add(v, u);
    	}
    	dfs1(1, 0);
    	dfs2(1, 0, 1);
    	cout << ans;
    	return 0;
    }
    
  • 相关阅读:
    边推改革边上“保险” 央行“双降”两大亮点带来哪些变化
    今天走势将冲高回落后重新回归下跌周期
    小心!资本正在流出中国:国际收支表里被遗漏的-2547亿美元
    价格改革确立时间表和路线图 六大重点领域破题
    避免在办公室体重上升的三大良策
    别再说自己有多忙
    沪指可能展开一波3个交易日的调整
    专车新政博弈 垄断行业改革样本
    JS和CS互访【后台前台代码调用JavaScript变量以及JavaScript调用代码变量】
    net9:图片文件转换成二进制流存入SQL数据库,以及从数据库中读取二进制流输出文件
  • 原文地址:https://www.cnblogs.com/lipoicyclic/p/15538901.html
Copyright © 2020-2023  润新知