7.17-雨天的尾巴
雨天的尾巴
题目背景
深绘里一直很讨厌雨天。
灼热的天气穿透了前半个夏天,后来一场大雨和随之而来的洪水,浇灭了一切。
虽然深绘里家乡的小村落对洪水有着顽固的抵抗力,但也倒了几座老房子,几棵老树被连根拔起,以及田地里的粮食被弄得一片狼藉。
无奈的深绘里和村民们只好等待救济粮来维生。
不过救济粮的发放方式很特别。
题目描述
首先村落里的一共有 \(n\) 座房屋,并形成一个树状结构。然后救济粮分 \(m\) 次发放,每次选择两个房屋 \((x, y)\),然后对于 \(x\) 到 \(y\) 的路径上(含 \(x\) 和 \(y\))每座房子里发放一袋 \(z\) 类型的救济粮。
然后深绘里想知道,当所有的救济粮发放完毕后,每座房子里存放的最多的是哪种救济粮。
输入格式
输入的第一行是两个用空格隔开的正整数,分别代表房屋的个数 \(n\) 和救济粮发放的次数 \(m\)。
第 \(2\) 到 第 \(n\) 行,每行有两个用空格隔开的整数 \(a, b\),代表存在一条连接房屋 \(a\) 和 \(b\) 的边。
第 \((n + 1)\) 到第 \((n + m)\) 行,每行有三个用空格隔开的整数 \(x, y, z\),代表一次救济粮的发放是从 \(x\) 到 \(y\) 路径上的每栋房子发放了一袋 \(z\) 类型的救济粮。
输出格式
输出 \(n\) 行,每行一个整数,第 \(i\) 行的整数代表 \(i\) 号房屋存放最多的救济粮的种类,如果有多种救济粮都是存放最多的,输出种类编号最小的一种。
如果某座房屋没有救济粮,则输出 \(0\)。
样例 #1
样例输入 #1
5 3
1 2
3 1
3 4
5 3
2 3 3
1 5 2
3 3 3
样例输出 #1
2
3
3
0
2
提示
- 对于 \(20\%\) 的数据,保证 \(n, m \leq 100\)。
- 对于 \(50\%\) 的数据,保证 \(n, m \leq 2 \times 10^3\)。
- 对于 \(100\%\) 测试数据,保证 \(1 \leq n, m \leq 10^5\),\(1 \leq a,b,x,y \leq n\),\(1 \leq z \leq 10^5\)。
思路
吧每个点都整一个线段树,然后由于是对x到y的路径上加z,所以是一个知识点树上差分。再进行线段树合并即可解决。
代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 1e5 + 10;
const int M = N * 80;
int deep[N], fa[N][20];
int ls[M], rs[M];
int sum[M], maxz[M], root[N];
int ans[N];
vector<int> ve[N];
int cnt;
int n, m;
// 初始化(ST表)
void dfs(int u, int father)
{
fa[u][0] = father;
deep[u] = deep[father] + 1;
for(int i = 1; i < 20; i ++)
fa[u][i] = fa[fa[u][i - 1]][i - 1]; //ST表
for(int i = 0; i < ve[u].size(); i ++)
{
int j = ve[u][i];
if(j != father) dfs(j, u);
}
}
// 求lca
int lca(int u, int v)
{
if(deep[u] < deep[v]) swap(u, v);
for(int i = 19; i >= 0; i --)
if(deep[fa[u][i]] >= deep[v]) u = fa[u][i];
if(v == u) return u;
for(int i = 19; i >= 0; i --)
{
if(fa[u][i] != fa[v][i])
u = fa[u][i], v = fa[v][i];
}
return fa[u][0];
}
//更新数据
/*void pushup(int rt)
{
if(sum[ls[rt]] > sum[rs[rt]])
sum[rt] = sum[ls[rt]], maxz[rt] = maxz[ls[rt]];
else if(sum[ls[rt]] < sum[rs[rt]])
sum[rt] = sum[rs[rt]], maxz[rt] = maxz[rs[rt]];
else sum[rt] = sum[rs[rt]], maxz[rt] = min(maxz[ls[rt]], maxz[rs[rt]]);
}*/
void pushup(int rt){
if(sum[ls[rt]] >= sum[rs[rt]])
sum[rt] = sum[ls[rt]], maxz[rt] = maxz[ls[rt]];
else sum[rt] = sum[rs[rt]], maxz[rt] = maxz[rs[rt]];
}
//线段树动态开点
void Update(int &rt, int l, int r, int z, int val)
{
if(!rt) rt = ++ cnt;
if(l == r) // 找到叶节点
{
sum[rt] += val; //该颜色 + 1;
maxz[rt] = z; //涂颜色;
return;
}
int mid = l + r >> 1;
if(z > mid) Update(rs[rt], mid + 1, r, z, val);
else Update(ls[rt], l, mid, z, val);
pushup(rt);
}
//线段树合并
int merge(int u, int v, int l, int r)
{
if(!u || !v) return u + v;
if(l == r)
{
sum[u] += sum[v];
return u;
}
int mid = l + r >> 1;
ls[u] = merge(ls[u], ls[v], l, mid);
rs[u] = merge(rs[u],rs[v], mid + 1, r);
pushup(u);
return u;
}
//计算答案(递推)
void cale(int u, int father)
{
for(int i = 0 ; i < ve[u].size(); i ++) //计算合并后的树
{
int j = ve[u][i];
if(j != father)
{
cale(j, u);
root[u] = merge(root[u], root[j], 1, N);
}
}
ans[u] = maxz[root[u]];
if(!sum[root[u]]) ans[u] = 0; //如果到该点的没有, 答案为0
}
int main()
{
cin >> n >> m;
for(int i = 1; i < n; i ++)
{
int a, b;
cin >> a >> b;
ve[a].push_back(b);
ve[b].push_back(a);
}
dfs(1, 0);
while(m --)
{
int a, b, z;
cin >> a >> b >> z;
Update(root[a], 1, N, z, 1);
Update(root[b], 1, N, z, 1);
Update(root[lca(a, b)], 1, N, z, -1);
Update(root[fa[lca(a, b)][0]], 1, N, z, -1); //树上差分
}
cale(1, 0);
for(int i = 1; i <= n; i ++) cout << ans[i] << endl;
}

浙公网安备 33010602011771号