【树型状态压缩动态规划】【NOI2006】网络收费

来源:互联网 发布:淘宝115会员暗号 编辑:程序博客网 时间:2024/06/11 06:02
【问题描述】网络已经成为当今世界不可或缺的一部分。每天都有数以亿计的人使用网络进行学习、科研、娱乐等活动。然而,不可忽视的一点就是网络本身有着庞大的运行费用。所以,向使用网络的人进行适当的收费是必须的,也是合理的。MY 市 NS 中学就有着这样一个教育网络。网络中的用户一共有 2N 个,编号依次为 1, 2, 3, ..., 2N。这些用户之间是用路由点和网线组成的。用户、路由点与网线共同构成一个满二叉树结构。树中的每一个叶子结点都是一个用户,每一个非叶子结点(灰色)都是一个路由点,而每一条边都是一条网线(见下图,用户结点中的数字为其编号)。

MY 网络公司的网络收费方式比较奇特,称为“ 配对收费 ”。即对于每两个用户 i, j (1≤i < j ≤2 ) 进行收费。由于用户可以自行选择两种付费方式 A、B中的一种,所以网络公司向学校收取的费用与每一位用户的付费方式有关。该费用等于每两位不同用户配对产生费用之和。为了描述方便,首先定义这棵网络树上的一些概念:祖先:根结点没有祖先,非根结点的祖先包括它的父亲以及它的父亲的祖先;管辖叶结点:叶结点本身不管辖任何叶结点,非叶结点管辖它的左儿子所管辖的叶结点与它的右儿子所管辖的叶结点;距离:在树上连接两个点之间的用边最少的路径所含的边数。对于任两个用户 i, j (1≤i<j≤2N ),首先在树上找到与它们距离最近的公共祖先:路由点 P,然后观察 P 所管辖的叶结点(即用户)中选择付费方式 A 与 B的人数,分别记为 nA 与 nB,接着按照网络管理条例第 X 章第 Y 条第 Z 款进行收费(如下表),其中 Fi, j 为 i 和 j 之间的流量,且为已知量。

由于最终所付费用与付费方式有关,所以 NS 中学的用户希望能够自行改变自己的付费方式以减少总付费。然而,由于网络公司已经将每个用户注册时所选择的付费方式记录在案,所以对于用户 i,如果他/她想改变付费方式(由 A 改为(修改付费方式记录)B 或由 B 改为 A)就必须支付 Ci 元给网络公司以修改档案,现在的问题是,给定每个用户注册时所选择的付费方式以及 Ci,试求这些用户应该如何选择自己的付费方式以使得 NS 中学支付给网络公司的总费用最少(更改付费方式费用+配对收费的费用)。【输入格式】输入文件中第一行有一个正整数 N。第二行有 2^N 个整数,依次表示 1号, 2号,..., 2^N 号用户注册时的付费方式,每一个数字若为 0,则表示对应用户的初始付费方式为 A,否则该数字为 1,表示付费方式为 B。第三行有 2^N 个整数,表示每一个用户修改付费方式需要支付的费用,依次为C1, C2, ...,CM 。( M=2^N)以下 2N-1 行描述给定的两两用户之间的流量表 F,总第(i + 3)行第 j 列的整数为 Fi, j+i 。(1≤i<2N,1≤j≤2N – i)所有变量的含义可以参见题目描述。【输出格式】你的程序只需要向输出文件输出一个整数,表示 NS 中学支付给网络公司的最小总费用。(单位:元)【输入样例】21 0 1 02 2 10 910 1 22 13【输出样例】8【样例说明】将 1 号用户的付费方式由 B 改为 A,NS 中学支付给网络公司的费用达到最小。【评分方法】本题没有部分分,你的程序的输出只有和我们的答案完全一致才能获得满分,否则不得分。【数据规模和约定】40%的数据中 N≤4;80%的数据中 N≤7;100%的数据中 N≤10,0≤Fi, j≤500,0≤Ci≤500 000。
考试的时候没有想到,这道题自底向上计算的同时,还需要自顶向下枚举状态并且用二进制数存储下来,这样才能计算出真正的总费用。
要真正进行动态规划之前,还需要先对问题作一下转化。
仔细观察付费计算那张表格,作出以下定义:
对于某一个结点,若nA < nB则称之为A付费结点;反之则为B付费结点。
那么对于任意两个不同的结点i, j,以及它们的最近公共祖先L,若i和L的付费方式相同,则相当于在L上产生F[i][j]的费用;若j和L的付费方式相同,则相当与再在L上产生F[i][j]的费用。
有了这个转化,就可以将费用分开计算了,即可以预处理出每个结点在它的每一个祖先上产生的费用cost[i][H]H为高度。

设f[u][B][S]表示以u为根的子树中,有B个用户的付费方式为B,它的所有祖先的状态为S(一个二进制数,分别维护高度从N到当前高度上一层的付费方式,0为A,1为B)的最小付费。
那么则有:f[u][B][S] = min{f[left_child][B1][S | this] + f[right_child][B2][S | this]}。(this表示u结点的付费情况。)
边界条件,当u为叶结点时,费用为sum from H = 1 to N cost[u - n + 1][H]。(这里n = 2 ^ N。)
另外当当前结点付费方式和初始付费方式不同时,还需要加上C[i]的值。
代码:

/********************************\ * @prob: NOI2006 network       * * @auth: Wang Junji            * * @stat: RE: 80                * * @date: June. 1st, 2012       * * @memo: 树型状态压缩动态规划     *\********************************/#include <cstdio>#include <algorithm>#include <cstring>const int maxN = 300, INF = 0x3f3f3f3f;bool chose[maxN];int C[maxN], F[maxN][maxN], f[maxN][maxN][maxN];int Anc[maxN][12], cost[maxN][12], n, N, ans = INF;void Dp(int u, int H, int S){    if (!H)    {        int i = u - n + 1;        f[u][chose[i]][S] = 0; f[u][!chose[i]][S] = C[i];        for (int k = N; k > 0; --k)            f[u][(S >> k) & 1][S] += cost[i][k];        return;    }    int lc = u << 1, rc = (u << 1) + 1, ths = 1 << H;    Dp(lc, H - 1, S); Dp(lc, H - 1, S | ths);    Dp(rc, H - 1, S); Dp(rc, H - 1, S | ths);    for (int B1 = 1; B1 <= ths >> 1; ++B1)    for (int B2 = (ths >> 1) - B1 + 1; B2 <= ths >> 1; ++B2)        f[u][B1 + B2][S] = std::min(f[u][B1 + B2][S],            f[lc][B1][S] + f[rc][B2][S]);    for (int B1 = 0; B1 <= ths >> 1; ++B1)    for (int B2 = 0; B1 + B2 <= ths >> 1; ++B2)        f[u][B1 + B2][S] = std::min(f[u][B1 + B2][S],            f[lc][B1][S | ths] + f[rc][B2][S | ths]);    return;}int main(){    freopen("network.in", "r", stdin);    freopen("network.out", "w", stdout);    scanf("%d", &N); n = 1 << N;    for (int i = 1; i < n + 1; ++i) scanf("%d", chose + i);    for (int i = 1; i < n + 1; ++i) scanf("%d", C + i);    for (int i = 1; i < n + 1; ++i)    for (int j = i + 1; j < n + 1; ++j)        scanf("%d", F[i] + j);    for (int i = 1; i < n + 1; ++i)    for (int j = 0, k = i + n - 1; j < N + 1; k >>= 1)        Anc[i][j++] = k;    //找到所有的i在高度为j的那一层的祖先为k。    for (int i = 1; i < n; ++i)    for (int j = i + 1; j < n + 1; ++j)    for (int k = 0; k < N + 1; ++k)    if (Anc[i][k] == Anc[j][k])    {cost[i][k] += F[i][j], cost[j][k] += F[i][j]; break;}    memset(f, 0x3f, sizeof f); Dp(1, N, 0);    for (int i = 0; i < n + 1; ++i)        ans = std::min(ans, f[1][i][0]);    printf("%d\n", ans); return 0;}