【题解】P4178 Tree

前置芝士

P3806 【模板】点分治1 。不过数据真是水的可以,第一次我数组开小,过了;第二次我分治的时候没找中心,还是过了……所以也可以做P4149 Race

题意

和点分治模板很像:求树上距离小于等于 k k k的路径数量。(把模板的等于改成了小于等于,并且需要统计路径数量)

分析

由于题目变成了小于等于,那么我们就不能再用原来那套开桶的办法了。于是我们考虑把当前根的所有子树中的节点拉出来统计方案。

具体方法是把所有子树中的节点按照到根的距离排序,然后两端开始双指针 O ( n ) O(n) O(n)扫描。(不会双指针的自行 G o o g l e Google Google


于是你自信满满地打了一发,却发现爆 0 0 0了!!!

实际上是由于有一些情况没有考虑到。

由于普通点分治的时候是对根的每一棵子树分别进行答案统计,也就意味着统计的路径都是不同子树间的,但我们这次把所有点都拉出来排序了,也就不能保证这条路径横跨两棵子树。于是就会产生下图的情况:

V24JYV.png

途中蓝色路径虽然经过科根节点,但显然是不合法的路径,需要减去。

这里用到一点小小的容斥,由于这种不合法的路径一定在根节点的同一棵子树内,于是我们先计算出经过这个子树的根的路径数量(上图中子树的根是1),然后减去即可。而对于子树中出现的不合法情况,我们接着用同样的方法容斥即可。

注意点:容斥的时候并不是真正统计子节点答案的时候,计算之前需要把儿子的 d i s dis dis值设为当前根节点到它的边权,因为这样的不合法路径一定会到达根节点,在统计的时候必须把经过两次的那条多余边减去。

代码

#include <bits/stdc++.h>
#define MAX 100005
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;

int n, k, cnt, rt, sum, tot;
int head[MAX], vet[MAX], Next[MAX], cost[MAX];
int dis[MAX], d[MAX], mx[MAX], sz[MAX], vis[MAX];
ll ans;

void add(int x, int y, int w){
    cnt++;
    Next[cnt] = head[x];
    head[x] = cnt;
    vet[cnt] = y;
    cost[cnt] = w;
}

void getrt(int x, int fa){		//找重心(点分治模板)
    sz[x] = 1, mx[x] = 0;
    for (int i = head[x]; i; i = Next[i]) {
        int v = vet[i];
        if(v == fa || vis[v]) continue;
        getrt(v, x);
        sz[x] += sz[v];
        mx[x] = max(mx[x], sz[v]);
    }
    mx[x] = max(mx[x], sum-sz[x]);
    if(mx[x] < mx[rt]) rt = x;
}

void getdis(int x, int fa){		//处理距离(模板)
    d[++tot] = dis[x];
    for (int i = head[x]; i; i = Next[i]) {
        int v = vet[i];
        if(v == fa || vis[v]) continue;
        dis[v] = dis[x]+cost[i];
        getdis(v, x);
    }
}

ll calc(int x, int w){		//计算贡献
    tot = 0;
    dis[x] = w;
    getdis(x, 0);		//处理出距离并排序
    sort(d+1, d+tot+1);
    int l = 1, r = tot;
    ll res = 0;
    while(l < r){		//双指针,从两头开始扫
        if(d[l]+d[r] <= k){
            res += r-l;
            l++;
        }
        else r--;
    }
    return res;
}

void solve(int x){
    vis[x] = 1;
    ans += calc(x, 0);
    for (int i = head[x]; i; i = Next[i]) {
        int v = vet[i];
        if(vis[v]) continue;
        ans -= calc(v, cost[i]);		//容斥,把儿子节点dis初值设为边权
        sum = sz[v];
        rt = 0, mx[rt] = INF;
        getrt(v, 0);
        solve(rt);
    }
}

int main()
{
    cin >> n;
    int x, y, w;
    for (int i = 1; i < n; ++i) {
        scanf("%d%d%d", &x, &y, &w);
        add(x, y, w);
        add(y, x, w);
    }
    cin >> k;
    mx[rt] = sum = n;
    getrt(1, 0);
    solve(rt);

    cout << ans << endl;

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述: 给定一些文件夹和文件,要求将它们按照层级关系输出成一个树形结构。 输入格式: 输入的第一行包含一个整数 n,表示文件夹和文件的个数。 接下来 n 行,每行包含一个字符串,表示一个文件夹或者文件。 其中,文件名和文件夹名都不包含空格和斜杠,文件名包含一个小数点,表示文件后缀。 输出格式: 输出一个树形结构,每行表示一个文件夹或者文件,按照层级关系缩进,文件夹名称后面要加上一个斜杠。 注意,最后一个文件夹或者文件名称后面不能有空格。 样例: 输入: 6 /root /etc /root/abcd.txt /root/bcd/ /etc/test/ /root/bcd/efg.txt 输出: /root /abcd.txt /bcd/ /efg.txt /etc /test/ 算法1 (模拟) $O(n)$ 思路: 本题需要我们输出文件夹和文件的层级结构,因此可以考虑使用哈希表记录每个文件夹和文件的层级结构。 对于每个文件夹和文件,我们可以通过判断其路径中"/"的数量来确定其所在的层级结构,具体来说,每个"/"表示一层。因此,我们可以将路径按"/"分开,然后统计"/"的数量,就可以得到该文件夹或文件所在的层级结构。 同时,由于本题要求输出树形结构,因此我们需要对每个文件夹和文件进行缩进处理,使其在输出时具有层级关系。具体来说,我们可以通过其所在的层级结构来确定输出时需要添加的缩进空格数量。 最后,我们可以按照文件夹和文件的层级结构从小到大的顺序进行输出,这样能够保证每个文件夹和文件的父节点已经被输出过了。 时间复杂度 哈希表的查询和插入操作都是常数级别的,因此总时间复杂度为 $O(n)$。 C++ 代码 算法2 (模拟) $O(nlogn)$ 思路: 本题可以使用字典树来实现,具体来说,我们可以将每个文件夹和文件的路径看作一个字符串,然后将所有字符串插入到字典树中。 同时,我们可以定义一个结构体,用来存储每个字符串的层级结构和缩进空格数量,具体来说,每个字符串的层级结构可以通过其在字典树中的深度来确定,而每个字符串的缩进空格数量则可以通过其所在的层级结构来确定。 最后,我们可以按照字符串的层级结构从小到大的顺序进行输出,这样能够保证每个字符串的父节点已经被输出过了。 时间复杂度 插入字符串的时间复杂度为 $O(nlogn)$,输出字符串的时间复杂度也为 $O(nlogn)$,因此总时间复杂度为 $O(nlogn)$。 C++ 代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值