hackrerank甚至树解决方案解释(具有偶数节点的森林)

时间:2016-01-29 06:44:25

标签: algorithm graph tree

我在hackerank上做了这个名为Even Tree的问题:

https://www.hackerrank.com/challenges/even-tree

最初我不知道如何切割边缘并在树外建造一片森林。所以我在网上查了一下,在stackeverflow上看到了这个答案:

Obtain forest out of tree with even number of nodes

好吧,只计算孩子的数量看起来更简单,我用C ++实现它:

#include <iostream>
#include <list>
#include <vector>
using namespace std;

int main() {
  int N, M, ans = 0;
  cin >> N >> M;
  vector<int> tree(N+1);
  vector<int> count(N+1);

  fill(count.begin(), count.end(), 1);
  for(int i = 0; i < M; i++) {
    int p, q;
    cin >> p >> q;
    tree[p] = q;

    int root = tree[p];

    // updating ancesors child count
    while(root) {
      count[root] += count[p];
      root = tree[root];
    }
  }

  int counter = 0;
  // displaying results
  for(int i = 2; i < count.size(); i++) {
    cout << count[i] << " ";
    if(count[i]%2 == 0)
      counter++;
  }
  cout <<"\nans : " <<  counter << endl;
  return 0;
}

我的问题是:这种方法有何用处?与选择具有最小边数的树相关联的子项数量?我不想复制解决方案,我想了解它背后的实际逻辑。请帮忙

1 个答案:

答案 0 :(得分:1)

首先,问题说所有测试用例(所有输入树),都有一种方法可以删除边缘,从而保留森林的均匀大小。当然,因为结果林中的每棵树都是偶数大小,这意味着节点N的总数必须是偶数。即使树必须有解决方案,因为至少他们可以删除0边缘以形成偶数森林。

请注意,有一些边缘 CAN NOT 被删除,即连接叶节点的边缘。因此,脑子里浮现出一些贪婪的想法,结果我可以证明它们,所以它们就成了解决问题的贪婪算法。

我声称

a)任何偶数大小的子树,我们都可以安全地从原始图中删除它们以减少问题而不影响结果,方法是删除子树根及其父级的边缘(如果存在) )

b)任何奇数大小的子树,我们都可以用一个节点替换整个子树来减少问题。子树根的边缘及其父 CAN NOT 将被删除(如果存在)

我们通过矛盾证明 a)。假设我们删除图形的偶数子树将使问题从可解决到无法解决。在删除偶数子树后考虑图形,如果它是奇数大小,那么原始图形也是不可解的;如果它的大小均匀,则在删除偶数子树后必须有解。两种情况都产生矛盾。

b)非常简单,因为子树是奇数大小,其奇偶校验与单个叶节点相同,必须与其父节点连接。因此,我们可以用单个节点替换子树来减少问题。

通过 a) b),我们可以从树的叶子做一个贪婪的算法(因为我们已经知道必须选择连接叶子的边缘,他们正在形成子树,我们可以从这里开始减少问题)

  1. 将所有树叶与父母联系起来
  2. 对于形成的所有子树,如果子树是偶数,则将全局答案计数器加1并删除子树;如果子树是奇数,则将其替换为单个节点(现在也是叶子)。
  3. 重复2直到最多剩下1个节点
  4. 那就是它。从概念上讲,这是算法。

    你可以使用许多不同的方法来实现它,我使用DFS直接将此算法转换为代码。

    有些只是计算子项的数量,因为这可以判断子树是奇数还是偶数。

    有人甚至只计算偶数度数的节点数! (这是迄今为止我所知道的最明智的解决方案)

    但它们都背后有相同的概念:确定子树是偶数大小的奇数,删除它们(并增加应答计数器)或用单个节点替换它们以减少问题。

    所有实现也共享相同的复杂度O(N)。

    这是我接受的代码:

    #include<bits/stdc++.h>
    using namespace std;
    
    int w[105][105] = {0}, v[105] = {0};
    int n,m,ans=0;
    
    int dfs(int x){
        v[x] = 1;
        int son = 0;
        for(int i=0; i<105;i++) 
            if(w[x][i] && !v[i]) son += dfs(i);
        if(son % 2) { ans++; return 0;}
        return 1;
    }
    
    int main() {
        scanf("%d%d", &n,&m);
        for(int i=0,a,b; i<m;i++){
            scanf("%d%d", &a,&b);
            w[a][b] = w[b][a] = 1;
        }
        dfs(1);
        printf("%d\n", ans-1);  
        return 0;
    }