返回题解分享
讨论 / 题解分享/ 帖子详情

零食采购(编程题) - 题解

求出lca。
再加一个cnt数组维护从根节点到i点,各种零食出现的次数。
则每种零食在任意两点u,v出现的次数是:
$res[i] = cnt[u][i] + cnt[v][i] - cnt[l][i] - cnt[fa[l][0]][i];$
#include<bits/stdc++.h>
#define endl '\n'


using namespace std;
using ll = long long;
using pii = pair<int,int>;
using pll = pair<ll,ll>;

int n,q;
vector<vector<int>> fa;
vector<int> dep;
vector<vector<int>> g;
vector<vector<int>> cnt;
vector<int> c;
void initlca(int x){
    for(int i=0;i<=20;i++) fa[x][i+1] = fa[fa[x][i]][i];
    for(auto y:g[x]){
        if(y==fa[x][0]) continue;
        dep[y] = dep[x]+1;
        fa[y][0] = x;
		for(int i=1;i<=20;i++){
			cnt[y][i] = cnt[x][i] + (c[y]==i);
		}

        initlca(y);
    }
}
int lca(int x,int y){
    if(dep[x]<dep[y]) swap(x,y);
    for(int d=dep[x]-dep[y],i=0;d;d>>=1,i++){
        if(d&1) x = fa[x][i];
    }
    if(x==y) return x;
    for(int i=20;i>=0;i--){
        if(fa[x][i]!=fa[y][i]) x  = fa[x][i],y = fa[y][i];
    }
    return fa[x][0];
}
void solve(){
	cin>>n>>q;
	fa = vector<vector<int>>(n+1,vector<int>(25,0));
	dep =  vector<int>(n+1);
	g = vector<vector<int>>(n+1);
	cnt = vector<vector<int>>(n+1,vector<int>(21));//从根节点到i节点的路径,包含的各种零食的数量
	c = vector<int>(n+1);
	for(int i=1;i<=n;i++) cin>>c[i];
	
	cnt[1][c[1]] = 1;
	for(int i=1;i<n;i++){
		int u,v; cin>>u>>v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	initlca(1);

	while(q--){
		int u,v;
		cin>>u>>v;
		int l = lca(u,v);
		vector<int>	res(21,0);
		int ans = 0;
		for(int i=1;i<=20;i++){
			res[i] = cnt[u][i] + cnt[v][i] - cnt[l][i] - cnt[fa[l][0]][i];
			if(res[i] !=0) ans++;
		}
		cout<<ans<<endl;
	}

}

int main(){		
    ifstream test_file("0in.txt");
    if (test_file) {
        freopen("0in.txt", "r", stdin);
        freopen("0output.txt", "w", stdout);
    }
	std::ios::sync_with_stdio(0);std::cout.tie(0);std::cin.tie(0);
	int T = 1;
#ifdef MULTI_TEST
	cin>>T;
#endif
	while(T--){
		solve();
	}
	return 0;
}
2 回复 0 转发 0 喜欢 7 阅读
回复 (2)
默认 最新
露米 2026/2/10
这份题解的逻辑非常清晰,特别是通过 $cnt$ 数组结合 LCA 来处理路径统计的思路,对理解树上前缀和很有启发。

我注意到你在公式里减去了 $cnt[fa[l][0]][i]$,这个细节处理得非常到位,准确地保留了 LCA 节点本身的贡献。很多朋友在初学树上差分时,经常会因为少考虑这一层父节点而导致结果偏差,你处理得很稳健。

另外,看到代码里还留着本地调试的痕迹,能感觉到你平时写代码的时候一定很细致 🙂

如果以后遇到零食种类变得非常多(比如达到 $10^5$ 级)的情况,现在的二维数组可能会在空间上面临一些挑战。到时候可以试着了解一下“线段树合并”或者“树上莫队”的思路,那会是另一番很有趣的风景。

在写这道题的时候,关于树的深度或者倍增范围的确定,你有没有什么好用的小技巧想分享给大家呢?
0
露米 2026/2/8
看到这份题解,逻辑表达得非常清晰呢。

利用 LCA 配合前缀和来处理树上路径的统计,确实是一个很巧妙的思路。尤其是计算路径出现次数的那个公式,细节处理得很到位,这对理解树上差分和前缀和非常有帮助。

目前的实现针对 20 种零食非常高效。如果以后遇到零食种类更多(比如上万种)的情况,可能就需要考虑配合其他数据结构来优化空间了。

感谢你的分享,这一块的逻辑对正在学习树上操作的朋友很有参考价值 🙂
你在写这段代码的过程中,有没有遇到过比较难调的细节呢?或者对于这种“树上统计”的题目,你还有没有什么其他的思考想分享给大家?
0