树状结构学习(1)、最大-最小划分树

来源:互联网 发布:淘宝的充值平台在哪里 编辑:程序博客网 时间:2024/05/21 01:53

树状结构学习(1)、最大-最小划分树

有这样一类题:
给一棵n个点的树,要求将它分成k个子树,使权值和最小的子树权值和尽量大。
解法一:二分答案
我们知道,如果已知权值下限,我们可以从下往上搜以O(n)的复杂度判断是否可以分成k个权值大于权值下限的子树。
缺点:没有真正解出这道题,如果权值是实数或很大将会使这种方法失效。

#include <iostream>#include <cmath>#include <cstring>#include <cstdio>#include <algorithm>#include <vector>using namespace std;const int MAXN = 1e5 + 10;int n, k;int size[MAXN];int bri[MAXN];int fa[MAXN];int w[MAXN];vector<int> map[MAXN];void dfs(int x, int father) {    size[x] = w[x];    for (int i = 0; i < map[x].size(); ++i) {      int y = map[x][i];      if (y == father) continue;       fa[y] = x;      dfs(y, x);      size[x] += size[y];    }}int tot;void dfs(int x, int father, int val) {    int delta = w[x];    for (int i = 0; i < map[x].size(); ++i) {      int y = map[x][i];      if (y == father) continue;      dfs(y, x, val);      delta += size[y];    }    size[x] = delta;    if (size[x] >= val) {      ++tot;      size[x] = 0;    }}bool check(int x) {    tot = 0;    for (int i = 1; i <= n; ++i)      bri[i] = size[i];    dfs(1, -1, x);    for (int i = 1; i <= n; ++i)      size[i] = bri[i];    if (tot >= k) return true;    else return false;}int main(){    freopen("in.txt", "r", stdin);    freopen("out1.txt", "w", stdout);    int a, b;    scanf("%d%d", &n, &k);    for (int i = 1; i < n; ++i) {      scanf("%d%d", &a, &b);      map[a].push_back(b);      map[b].push_back(a);    }    int l = 1, r = 0;    for (int i = 1; i <= n; ++i)      scanf("%d", &w[i]), r += w[i];    r /= k;    dfs(1, -1);    while (l < r) {      int mid = (l + r + 1) >> 1;      if (check(mid))        `l = mid;      else        r = mid - 1;    }    printf("%d", l);     return 0;} 

解法二:
移动割法
算法流程:
(1)选择入度为1的结点作为起始,将k-
1个割都放在与根相连的唯一的边上。
(2)计算当前划分的最小值Wmin
(3)移动割,并求出所有移动方法最大的子树权值和Wmax
(4)如果Wmax > Wmin,那么移动,并跳到(2)
(5)结束。Wmin为所求。当前割的位置即为一个可行的最优方案。

算法证明之后传。

#include <iostream>#include <cstdio>#include <cmath>#include <cstring>#include <algorithm>#include <vector>using namespace std;const int MAXN = 100000 + 10;const int INF = 0x7fffffff;int n, k, Wmin, start; int in[MAXN];   //入度 int size[MAXN];int fa[MAXN];   //fatherint vec[MAXN];  //每一个割现在的容量int have[MAXN]; //结点x是否有一个割 int pos[MAXN];  //割的位置 int w[MAXN];    //权重 int totSize = 0;vector<int> map[MAXN];void dfs(int x, int father) {    size[x] = w[x];    totSize += size[x];    for (int i = 0; i < map[x].size(); ++i) {      int y = map[x][i];      if (y == father) continue;      fa[y] = x;      dfs(y, x);       size[x] += size[y];    }}bool down() {    int Wmax = 0, next = 0, id = 0;    for (int i = 1; i <= k; ++i) {      int x = pos[i];      for (int j = 0; j < map[x].size(); ++j) {        int y = map[x][j];        if (y == fa[x] || have[y]) continue;        if (size[y] >= Wmin && size[y] > Wmax) {          Wmax = size[y];          next = y;          id = i;        }      }    }    if (!next) return false;    --have[pos[id]];    int tmp = size[pos[id]] - size[next], i = fa[pos[id]], flag = 1;    while (i && flag) {      if (have[i]) flag = 0;      size[i] += tmp;      i = fa[i];    }    size[pos[id]] -= size[next];    pos[id] = next;     ++have[next];    return true;} int query() {/*  cout<<"Status of have[]"<<endl;    for (int i = 1; i <= n; ++i)      printf("(%d, %d), ", i, have[i]);    cout<<endl;    cout<<"Status of size[]"<<endl;    for (int i = 1; i <= k; ++i)      cout<<size[pos[i]]<<" ";*/    int res = INF, sum = 0;    for (int i = 1; i <= k; ++i)      if (pos[i] != pos[i-1]) {        res = min(res, size[pos[i]]);        sum += size[pos[i]];      }//  cout<<totSize - sum<<endl;    res = min(res, totSize - sum);    return res;}int move(int start) {    Wmin = w[start];    while (true) {      if (!down()) break;      Wmin = query();    }    return Wmin;      }int main(){    freopen("in.txt", "r", stdin);    freopen("out2.txt", "w", stdout);    int a, b, Wstart = INF;    scanf("%d%d", &n, &k);    --k;    for (int i = 1; i < n; ++i) {      scanf("%d%d", &a, &b);      map[a].push_back(b);      map[b].push_back(a);      ++in[a];      ++in[b];    }    for (int i = 1; i <= n; ++i) {      scanf("%d", &w[i]);      if ((in[i] == 0 || in[i] == 1) && w[i] < Wstart)        start = i, Wstart = w[i];    }    dfs(start, -1);    if (k == 0) printf("%d", totSize);    else {        for (int i = 1; i <= k; ++i)          pos[i] = map[start][0];        have[map[start][0]] = k;        printf("%d", move(start));      }    return 0;}

附:随机数生成程序

#include <iostream>#include <cstdio>#include <cmath>#include <cstring>#include <algorithm>#include <ctime>using namespace std;int main(){    srand(time(0));    freopen("in.txt", "w", stdout);     int n = rand()%10000 + 1, k = rand()%n / 2 + 1;    cout<<n<<" "<<k<<endl;    for (int i = 2; i <= n; ++i)      cout<<rand()%(i-1)+1<<" "<<i<<endl;;    for (int i = 1; i <= n; ++i)      cout<<rand()%100000 + 1<<endl;    return 0;}
阅读全文
0 0
原创粉丝点击