Free tour II SPOJ - FTOUR2 && 点分治

博客介绍了如何利用点分治和启发式合并策略解决SPOJ上的FTOUR2问题。在给定一棵树和限制条件(经过的黑色点数不超过K)下,寻找路径长度最大值。博主经历了从二分到线段树,再到启发式合并的思路转变,最终实现nlogn的时间复杂度解法。
摘要由CSDN通过智能技术生成

题目大意

给你一棵树,树上的节点有白点有黑点,其中找到一条路径使得在经过的黑色点数不超过K的时候 路径长度最大,输出这个最大值

input

8 2 3 (n, k, m)
3
5
7 (m个黑点)
1 3 1
2 3 10
3 4 -2
4 5 -1
5 7 6
5 6 5
4 8 3 (边的连接情况以及权值)

output

12

idea

我做这道题做了好久啊。
这道题的核心就是怎么维护一条路上黑点个数在0~x范围内的路径最大值。我一开始没有想好,用的二分,后来wa了,仔细想了想,这个维护不了。又用了线段树。查询只需要logn,但是递归完一个子树的时候需要把线段树重新初始化,这样复杂度又变成了n2。
看了别人的代码,当时做这个题的时候也是看了很久很久,看得自己也是稀里糊涂的,昨天晚上整理,去看了一下启发式合并,虽然那个题没看懂,但是听他们一说,大约明白了启发式合并的意思,就是把小的集合往大的集合合并。
比如[1, 2], [1,2,3,4,5,6,7…1e9] 我们要合并这两个集合,如果把第二个集合合并到第一个集合,复杂度很大,所以要把第一个集合合并到第二个集合。
这里就很巧妙地用到了这个思想。
当我们跑到以root为根的另一个子树的时候,对于正在跑的该点,需要找已经跑过的子树符合条件的并且路径最大的(比如黑点的个数不能超过10,跑到当前点黑点的个数已经有3个了,我们就要去之前跑过的子树里找黑点个数在0~7范围内的路径的最大值),然后想了很多办法发现都不行,只能暴力去跑。然后就知道了要用启发式合并。先把每棵子树的黑点个数的最大值跑出来,排个序,每次用在黑点个数少的里面去找合法的。
然后时间复杂度是nlogn

code

#include <bits/stdc++.h>
using namespace std;

#define lson Root << 1
#define rson Root << 1 | 1
#define MID int mid = (l + r) / 2
const int maxn = 500000;
const int INF = 0x3f3f3f3f;

struct node {
   int v, w;
};

int k, allNode, sonMax[maxn], sonNum[maxn], ans, root, n;
bool vis[maxn], black[maxn];
vector<node> Edge[maxn];


void getroot(int now, int pre) {
   sonNum[now] = 1;
   sonMax[now] = 0;
   for(int i = 0; i < Edge[now].size(); i++) {
      int to = Edge[now][i].v;
      if(to == pre || vis[to]) continue;
      getroot(to, now);
      sonNum[now] += sonNum[to];
      sonMax[now] = max(sonMax[now], sonNum[to]);
   }
   sonMax[now] = max(sonMax[now], allNode - sonNum[now]);
   if(sonMax[now] < sonMax[root]) root = now;
}

int blackNum[maxn], dist[maxn], Max_Black;
//blackNum[i] 是i点的黑点个数
//dist[i] 是i点到根的距离
//Max_Black 是该子树黑点的最大值

void Get_Max_Black(int now, int pre) { //求该子树黑点的最大值
   Max_Black = max(Max_Black, blackNum[now]);
   for(int i = 0; i < Edge[now].size(); i++) {
      int to = Edge[now][i].v;
      if(vis[to] || to == pre) continue;
      blackNum[to] = blackNum[now] + black[to];
      dist[to] = dist[now] + Edge[now][i].w;
      Get_Max_Black(to, now);
   }
}

vector<pair<int, int> >sonTree; //first存黑点数最大值,second存是哪颗子树
int Max_dist[maxn]; //Max_dist[i] 是黑点数为i的距离最大值

void Get_dist(int now, int pre) { //得到Max_dist数组
   int num = blackNum[now];
   Max_dist[num] = max(Max_dist[num], dist[now]);

   for(int i = 0; i < Edge[now].size(); i++) {
     int to = Edge[now][i].v;
     if(vis[to] || to == pre) continue;
     Get_dist(to, now);
   }
}

int Max[maxn];

void Deal_son_tree(int now) {
   sonTree.clear();
   for(int i = 0; i < Edge[now].size(); i++) {
      int to = Edge[now][i].v;
      if(vis[to]) continue;
      Max_Black = 0;
      dist[to] = Edge[now][i].w;
      blackNum[to] = black[to];
      Get_Max_Black(to, now); //求该子树黑点的最大值
      sonTree.push_back(make_pair(Max_Black, to));
   }

   sort(sonTree.begin(), sonTree.end());

   for(int i = 0; i < sonTree.size(); i++) {  //启发式合并
       Get_dist(sonTree[i].second, now);
       int num = 0;
       if(i) {
           for(int j = sonTree[i].first; j >= 0; j--) {
              while(num + j < k && num < sonTree[i - 1].first) {
                  num++;
                  Max[num] = max(Max[num], Max[num - 1]);
              }
              if(j + num <= k) ans = max(ans, Max[num] + Max_dist[j]);
          }
       }
       if(i == sonTree.size() - 1) {
          for(int j = 0; j <= sonTree[i].first; j++) {
              if(j <= k) ans = max(max(Max[j], Max_dist[j]), ans);
              Max[j] = Max_dist[j] = 0;
          }
       }
       else {
          for(int j = 0; j <= sonTree[i].first; j++) {
             Max[j] = max(Max[j], Max_dist[j]);
             Max_dist[j] = 0;
          }
       }
   }
}

void solve(int now) {
   vis[now] = true;
   if(black[now]) k--;
   Deal_son_tree(now);
   if(black[now]) k++;

   for(int i = 0; i < Edge[now].size(); i++) {
      int to = Edge[now][i].v;
      if(vis[to]) continue;
      sonMax[0] = allNode = sonNum[to];
      getroot(to, root = 0);
      solve(root);
   }
}

int main()
{
    int m;
    scanf("%d %d %d", &n, &k, &m);
    for(int i = 0; i < m; i++) {
        int u;
        scanf("%d", &u);
        black[u] = true;
    }
    for(int i = 1; i < n; i++) {
        int u, v, w;
        scanf("%d %d %d", &u, &v, &w);
        Edge[u].push_back((node){v, w});
        Edge[v].push_back((node){u, w});
    }

    ans = 0;
    sonMax[0] = allNode = n;
    getroot(1, root = 0);
    solve(root);
    printf("%d\n", ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值