题意:
有一棵树,需要给其所有节点染色,每个点染色所需的时间是一样的都是1.给每个点染色,还有一个开销“当前时间×ci”,ci是每个节点的一个权值。染色还有另一个约束条件,要染一个点必须要先染好其父节点,所以第一个染的点是根节点。
求最小开销。
解法:
小班课比较详细地给出了解法和证明。(以下已经把每个点看成一个点集,独立的一个点可看做只有一个元素的点集,每个点集中的点有顺序,并且需要依次连续地染色,也就是染完第一个点后,需要立刻依次染之后的点)
每次找一个最大权值的非根点集,将它和它的父点集合并。新的点集的权值是(新点集中所有点的权值和)/(新点集中点的个数)。这里点集中的点有顺序,合并时,子点集的点放在父点集的点后面。
进行上述步骤,直到只剩下一个根点集。这时候,根点集中点的顺序,就是染色的顺序。
证明:
(i)先看每个都是点的情况。给出一个结论:如果这棵树中有最大权值点x(非根),那么一旦x的父节点已经染色,就应该立刻染x。这比较好证:设x的父节点为y,染色顺序为ya...bx,a...b这一段是别的一些可以染的点。这个染色顺序的意思是说染完x的父节点y之后,先染了一些别的点,再染x。
如果我们把b和x的染色顺序交换,显然是可行的,并且会更优:比较xb和bx,使用前者我们的收益(少掉的开销)是Cx-Cb,x少等了1的时间,b多等了1的时间。显然,收益Cx-Cb是正的,因为x的权值是当前最大的。这样我们也可以证,染完y之后应该立刻染x,所以可以把y和x合并成一个点集。
(ii)现在看都看做点集的情况,证明合并后以平均权值作为新的权值是合理的。
一个结论:如果这棵树中有最大平均权值点集X(非根),那么一旦X的父点集已经染色,就应该立刻染X。
这和(i)类似:设X的父子集Y,染色顺序YA...BX,A...B是别的一些可以染的点集。下证B和X染色顺序互换可以更优。设B的总权值为SB,点数为NB;X的为SX和NX。XB和BX比较,使用前者的收益是SX×NB- SB×NX,这相当与X中的每个点都少等待NB时间,而B中的每个点都多等了NX的时间。又因为X是最大平均权值点集,所以权值SX/NX>SB/NB,所以可以确保收益SX×NB-SB×NX是正的。
#include <cstdio>
#include <cstdlib>
#include <string>
#include <cstring>
#include <vector>
#define maxn 1005
using namespace std;
class node
{
public:
int value_sum;//点集中所有点的权值和
int num;//点集中点的个数
int father;
int set_id;//所属点集编号
bool is_setrep;//是不是点集的首个元素
double value;//权值平均
vector<int> in_set;//点集中其他点
vector<int> son;//该点集的子点集
node()
{
num = 1;
father = -1;
is_setrep = true;
}
};
node no[maxn];
int seq[maxn] = {0};//最后的涂的顺序的序列
int c[maxn] = {0};//涂每个点的花费
int n = 0, r = 0;
void init()
{
memset(no, 0, sizeof(no));
for (int i = 1; i <= n; ++i)
{
no[i].num = 1;
no[i].father = -1;
no[i].is_setrep = true;
}
}
//将编号为a的点集与其父点集合并
void union_node(int a)
{
int fa = no[a].father;
int son_num = no[a].son.size();
//printf("sonnum: %d\n", son_num);
no[fa].value_sum += no[a].value_sum;
no[fa].num += no[a].num;
no[fa].value = (double)(no[fa].value_sum) / no[fa].num;
no[a].set_id = fa;
no[a].is_setrep = false;
no[fa].in_set.push_back(a);
//printf("value: %f\n", no[fa].value);
for (int i = 0; i < no[a].num - 1; ++i)
{
no[fa].in_set.push_back(no[a].in_set[i]);
no[no[a].in_set[i]].set_id = fa;
}
for (int i = 0; i < son_num; ++i)
{
no[no[a].son[i]].father = fa;
no[fa].son.push_back(no[a].son[i]);
}
}
int main()
{
while(true)
{
scanf("%d%d", &n, &r);
init();
if (n == 0)
break;
int tmp = 0;
for (int i = 1; i <= n; ++i)
{
scanf("%d", &tmp);
c[i] = tmp;
no[i].value = no[i].value_sum = tmp;
no[i].set_id = i;
}
int tmp1 = 0, tmp2 = 0;
for (int i = 0; i < n - 1; ++i)
{
scanf("%d%d", &tmp1, &tmp2);
if(tmp1 == 0)
break;
no[tmp1].son.push_back(tmp2);
no[tmp2].father = tmp1;
}
int cur_num = n;
while(true)
{
if (cur_num == 1)
break;
double max_v = 0;
int max_i = 0;
for (int i = 1; i <= n; ++i)
{
//每次找不是根节点的最大权值子集与其父子集合并
if (no[i].is_setrep && i != r && max_v < no[i].value)
{
max_v = no[i].value;
max_i = i;
}
}
//printf("%d\n", max_i);
union_node(max_i);
--cur_num;
}
seq[1] = c[r];
for (int i = 0; i < n - 1; ++i)
{
seq[i + 2] = c[no[r].in_set[i]];
}
int ans = 0;
for (int i = 1; i <= n; ++i)
{
ans += i*seq[i];
}
printf("%d\n", ans);
}
return 0;
}
所以这样合并没有问题。 所以这样合并没有问题。