砍树

Posted onlyblues

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了砍树相关的知识,希望对你有一定的参考价值。

砍树

给定一棵由 $n$ 个结点组成的树以及 $m$ 个不重复的无序数对 $(a_1,b_1),(a_2,b_2), \\ldots ,(a_m,b_m)$,其中 $a_i$ 互不相同,$b_i$ 互不相同,$a_i \\ne b_j \\ (1 \\leq i,j \\leq m)$。

小明想知道是否能够选择一条树上的边砍断,使得对于每个 $(a_i,b_i)$ 满足 $a_i$ 和 $b_i$ 不连通,如果可以则输出应该断掉的边的编号(编号按输入顺序从 $1$ 开始),否则输出 $−1$。

输入格式

输入共 $n+m$ 行,第一行为两个正整数 $n,m$。

后面 $n−1$ 行,每行两个正整数 $x_i,y_i$ 表示第 $i$ 条边的两个端点。

后面 $m$ 行,每行两个正整数 $a_i,b_i$。

输出格式

一行一个整数,表示答案,如有多个答案,输出编号最大的一个。

数据范围

对于 $30\\%$ 的数据,保证 $1 < n \\leq 1000$。
对于 $100\\%$ 的数据,保证 $1 < n \\leq 10^5$,$1 \\leq m \\leq \\fracn2$。

输入样例:

6 2
1 2
2 3
4 3
2 5
6 5
3 6
4 5

输出样例:

4

样例解释

断开第 $2$ 条边后形成两个连通块:$\\3,4\\$,$\\1,2,5,6\\$,满足 $3$ 和 $6$ 不连通,$4$ 和 $5$ 不连通。

断开第 $4$ 条边后形成两个连通块:$\\1,2,3,4\\$,$\\5,6\\$,同样满足 $3$ 和 $6$ 不连通,$4$ 和 $5$ 不连通。

$4$ 编号更大,因此答案为 $4$。

 

解题思路

  由于在树中任意两点间的路径是固定的,因此要使得数对$(a_i,b_i)$对应的两个点不连通,那么只需要把$a_i \\to b_i$路径上的任意一条边砍掉即可。因此可以枚举所有的数对$(a_i,b_i)$,并对$a_i \\to b_i$路径上的所有边都累加$1$,表示砍掉这条边可以使得一个数对不连通。最后只需要遍历树中所有边找出被累加了$m$次的边并找到最大的编号。

  由于需要对树中某条路径上的边都加上某个数,因此需要用到树上差分中的边差分,关于树上差分的简单介绍可以参考链接。同时还需要用哈希表存边$(a_i,b_i)$与编号的映射,为了避免使用 std::map 这里把二元组$(a_i,b_i)$映射为$a_i \\times 100001 + b_i$,由于$b_i \\leq 10^5$因此$P$取到$100001$,相当于把$P$进制数转换为十进制数。然后再用 std::unordered_map 来存边与编号的映射关系。当然如果是cf的题还是老老实实用 std::map 吧(悲。

  AC代码如下,时间复杂度为$O(n + m \\logn)$:

 #include <bits/stdc++.h>
 using namespace std;
 
 typedef long long LL;
 
 const int N = 1e5 + 10, M = N * 2;
 
 int n, m;
 int head[N], e[M], ne[M], idx;
 int fa[N][17], dep[N];
 int q[N], hh, tt = -1;
 unordered_map<LL, int> mp;
 int d[N];
 int ans = -1;
 
 void add(int v, int w) 
     e[idx] = w, ne[idx] = head[v], head[v] = idx++;
 
 
 LL get(int x, int y) 
     return x * 100001ll + y;
 
 
 int lca(int a, int b) 
     if (dep[a] < dep[b]) swap(a, b);
     for (int i = 16; i >= 0; i--) 
         if (dep[fa[a][i]] >= dep[b]) a = fa[a][i];
     
     if (a == b) return a;
     for (int i = 16; i >= 0; i--) 
         if (fa[a][i] != fa[b][i]) a = fa[a][i], b = fa[b][i];
     
     return fa[a][0];
 
 
 void dfs(int u, int pre) 
     for (int i = head[u]; i != -1; i = ne[i]) 
         if (e[i] != pre) 
             dfs(e[i], u);
             d[u] += d[e[i]];
         
     
     if (pre != -1 && d[u] == m) ans = max(ans, mp[get(u, pre)]);
 
 
 int main() 
     scanf("%d %d", &n, &m);
     memset(head, -1, sizeof(head));
     for (int i = 1; i < n; i++) 
         int v, w;
         scanf("%d %d", &v, &w);
         add(v, w), add(w, v);
         mp[get(v, w)] = mp[get(w, v)] = i;
     
     memset(dep, 0x3f, sizeof(dep));
     dep[0] = 0, dep[1] = 1;
     q[++tt] = 1;
     while (hh <= tt) 
         int t = q[hh++];
         for (int i = head[t]; i != -1; i = ne[i]) 
             if (dep[e[i]] > dep[t] + 1) 
                 dep[e[i]] = dep[t] + 1;
                 q[++tt] = e[i];
                 fa[e[i]][0] = t;
                 for (int j = 1; j <= 16; j++) 
                     fa[e[i]][j] = fa[fa[e[i]][j - 1]][j - 1];
                 
             
         
     
     for (int i = 0; i < m; i++) 
         int a, b;
         scanf("%d %d", &a, &b);
         d[a]++, d[b]++, d[lca(a, b)] -= 2;
     
     dfs(1, -1);
     printf("%d", ans);
     
     return 0;
 

cogs luogu 砍树

★   输入文件:eko.in   输出文件:eko.out   简单对比
时间限制:1 s   内存限制:256 MB

【题目描述】

N棵树,每棵都有一个整数高度。有一个木头的总需要量M。

现在确定一个最大的统一的砍树高度H,如果某棵树的高度大于H,则高出的部分被砍下。使得所有被砍下的木材长度之和达到M(允许稍超过M)。

例如,有4棵树,高度分别是20 15 10 17, 需要的木材长度为 7,砍树高度为15时,第1棵树被砍下5,第4棵树被砍下2,得到的总长度为7。如果砍树高度为16时,第1棵树被砍下4,第4棵树被砍下1,则得到的木材数量为5。

【输入格式】

第1行:2个整数N和M,N表示树木的数量(1 ≤ N ≤ 1 000 000),M表示需要的木材总长度(1 ≤ M ≤ 2 000 000 000)。

第2行: N个整数表示每棵树的高度,值均不超过1  000  000  000。所有木材高度之和大于M,因此必然有解。

【输出格式】

第1行:1个整数,表示砍树的最高高度。

【样例输入】

5 20
4 42 40 26 46

【样例输出】

36
 1 #include<iostream>
 2 #include<cstdio>
 3 
 4 #define ll long long
 5 using namespace std;
 6 const int N=1000010;
 7 
 8 ll a[N];
 9 ll n,m;
10 
11 inline ll read()
12 {
13     ll x=0;
14     char c=getchar();
15     while(c<0||c>9)c=getchar();
16     while(c>=0&&c<=9)x=x*10+c-0,c=getchar();
17     return x;
18 }
19 
20 inline bool pd(int now)
21 {
22     ll answer=0;
23     for(int i=1;i<=n;i++)
24         if(a[i]-now>0)answer+=(a[i]-now);
25     if(answer>=m)return 1;
26     else return 0;
27 }
28 
29 int main()
30 {
31     freopen("eko.in","r",stdin);
32     freopen("eko.out","w",stdout);
33     n=read();
34     m=read();
35     ll r=-1;
36     for(int i=1;i<=n;i++)
37     {
38         a[i]=read();
39         r=max(r,a[i]);
40     }    
41     
42     ll l=1;
43     ll mid;
44     while(l<r)
45     {
46         mid=(r-l)/2+l;
47         if(pd(mid))l=mid+1;
48         else r=mid;
49     }
50     printf("%lld",l-1);
51     return 0;
52 }

 

以上是关于砍树的主要内容,如果未能解决你的问题,请参考以下文章

codevs1369 xth 砍树(线段树)

HNIEOJ 3964: 砍树

砍树

codevs 1388 砍树

cogs luogu 砍树

Leetcode 675.为高尔夫比赛砍树