题目链接
思路
一开始想着直接并查集,然后开个结果数组,每次传递信息,都假设这个信息是从父节点开始传递的,让父节点的结果数组加上发送的消息数量就可以,然后子节点的信息数,就是子节点的结果集加上父节点的结果集,就像下面,假设有两个点(1,2),进行两个操作,将1和2连接,在2上发送信息
这个时候就会有一个问题,假设在1还没有和2连接的时候就发送了消息,然后再连接,此时查询结果,会产生2没连在1上时发送的消息也被算在2内的现象,即:
很明显,结果应该为 1 和 0 ,但是依照之前的思路结果却为 1 和 1 ,此时我们考虑连上一条边,就把这条边先减去父节点消息数,有点差分的意思,像下图
此时结果就正确了。非常自信的认为自己正确了,自信交卷准备一发入魂,结果就看到了
WRONG ANSWER
开始怀疑人生,到底是哪里出了问题,跑去看了一下测试点,手动用草稿纸模拟了一边,终于让我发现了问题所在
如果连接的两个点,本身自身就带着一堆的子节点,两个父节点相连,让连上去的父节点减去最终的父节点的值,连上去的父节点没问题,但是它的所有子节点都没有去减掉父节点的值,类似下图
发现问题所在,那么接下来就是解决问题了,跑去上了个厕所,灵感喷涌而出,都说厕所很有灵感果然是真的(。。。)
我们在每一次加上点的时候,都先把被加上点的所有子节点都去减去最终父节点的值,同时加上被加上点的值就可以了,只需要开一个辅助的vector就能完美的完成这件事,思路清奇,非常完美。
代码
#include<iostream>
#include<vector>
using namespace std;
int father[10005];
int result[10005];
vector<int> v[10005];
void init(int n){
for(int i = 1;i <= n;i++)
father[i] = i;
}
int find(int x){
return x == father[x] ? x : father[x] = find(father[x]);
}
void unite(int x , int y){
int a = find(x);
int b = find(y);
int m = min(a , b);
int ma = max(a , b);
father[a] = m;
father[b] = m;
int len = v[ma].size();
v[m].push_back(ma);
for(int i = 0; i < len ;i++){
v[m].push_back(v[ma][i]);
result[v[ma][i]] -= result[m];
result[v[ma][i]] += result[ma];
}
result[ma] -= result[m];
v[ma].clear();
}
int main(){
int n , m;
cin >> n >> m ;
init(n);
for(int i = 1;i <= m;i++){
int op , p , t;
cin >> op >> p >> t;
if(op == 1){
if(find(p) == find(t))
continue;
unite(p , t);
}else{
int x = find(p);
result[x] += t;
}
}
for(int i = 1;i <= n;i++){
int x = find(i);
if(x == i){
cout << result[i] << " ";
}else{
cout << result[i] + result[x] << " ";
}
}
cout << endl;
return 0;
}
终于一道简单题 ACCEPT