洛谷 P1273 【有线电视网】

题目描述

某收费有线电视网计划转播一场重要的足球比赛。他们的转播网和用户终端构成一棵树状结构,这棵树的根结点位于足球比赛的现场,树叶为各个用户终端,其他中转站为该树的内部节点。

从转播站到转播站以及从转播站到所有用户终端的信号传输费用都是已知的,一场转播的总费用等于传输信号的费用总和。

现在每个用户都准备了一笔费用想观看这场精彩的足球比赛,有线电视网有权决定给哪些用户提供信号而不给哪些用户提供信号。

写一个程序找出一个方案使得有线电视网在不亏本的情况下使观看转播的用户尽可能多。

输入

输入文件的第一行包含两个用空格隔开的整数N和M,其中2≤N≤3000,1≤M≤N-1,N为整个有线电视网的结点总数,M为用户终端的数量。

第一个转播站即树的根结点编号为1,其他的转播站编号为2到N-M,用户终端编号为N-M+1到N。

接下来的N-M行每行表示—个转播站的数据,第i+1行表示第i个转播站的数据,其格式如下:

K A1 C1 A2 C2 … Ak Ck

K表示该转播站下接K个结点(转播站或用户),每个结点对应一对整数A与C,A表示结点编号,C表示从当前转播站传输信号到结点A的费用。最后一行依次表示所有用户为观看比赛而准备支付的钱数。

输出

输出文件仅一行,包含一个整数,表示上述问题所要求的最大用户数。

样例输入

5 3
2 2 2 5 3
2 3 2 4 3
3 4 2

样例输出

2

提示

如下图所示,共有五个结点。结点①为根结点,即现场直播站,②为一个中转站,③④⑤为用户端,共M个,编号从N-M+1到N,他们为观看比赛分别准备的钱数为3、4、2,从结点①可以传送信号到结点②,费用为2,也可以传送信号到结点⑤,费用为3(第二行数据所示),从结点②可以传输信号到结点③,费用为2。也可传输信号到结点④,费用为3(第三行数据所示),如果要让所有用户(③④⑤)都能看上比赛,则信号传输的总费用为:

2+3+2+3=10,大于用户愿意支付的总费用3+4+2=9,有线电视网就亏本了,而只让③④两个用户看比赛就不亏本了。

我用的是多叉树转二叉树的方法,这题只要分四种情况考虑就行了:

① 这是用户节点,且没有兄弟(右儿子);

② 这是一个用户节点,但有右子(兄弟节点);

③ 这是转播站,且没有兄弟节点;

④ 这是个转播站,且有兄弟节点。

详细过程可以看代码,里面有很详细的注释:

#include <cstdio>

struct tree {
    int lc,rc; //多叉树转二叉树:左儿子右兄弟
    int val; //选择选择节点i时能赚多少钱(该节点的值(如果是中转站则为0)-父节点连到这个节点的边的值)
    int num; //表示该节点为根的树,最多有多少个用户节点
};

const int maxn=3000;
int n,m;
tree a[maxn+1];
int dp[maxn+1][maxn+1];

int maximum(int x, int y) {
    if (x>y) return x;
    return y;
}

//计算以x为根的树最多有多少个用户节点
int count(int x) {
    int res=0;
    //如果自己是用户节点,答案+1
    if (a[x].lc==0)
        res++;
    //如果有左右子,则递归统计
    if (a[x].rc!=0)
        res+=count(a[x].rc);
    if (a[x].lc!=0)
        res+=count(a[x].lc);
    a[x].num=res;
    return res;
}

void init() {
    scanf("%d%d",&n,&m);
    //第i个转播站的信息
    for (int i=1; i<=n-m; i++) {
        int ii,k,vv;
        scanf("%d",&k);
        //对k=1的特殊处理,要放在这个转播站的左儿子上
        scanf("%d%d",&ii,&vv);
        a[i].lc=ii;
        a[ii].val-=vv;
        //其余的放在之前处理过的兄弟节点的右儿子上
        for (int j=2; j<=k; j++) {
            int x,v;
            scanf("%d%d",&x,&v);
            a[ii].rc=x;
            a[x].val-=v;
            ii=x;
        }
    }
    //用户节点的数据
    for (int i=n-m+1; i<=n; i++) {
        int x;
        scanf("%d",&x);
        a[i].val+=x;
    }
    //计算结构体中的num的值,搜索时有用
    count(1);
}

//dp[root][x]表示以root为根的树,里面有x个用户节点,所能赚到的最多的钱数
int dfs(int root, int x) {
    if (dp[root][x]!=0) return dp[root][x]; //记忆化搜索
    if (x==0) return 0; //如果没有连任何一个用户节点,则不赚不亏,返回0
    //如果这是用户节点,且没有兄弟(右儿子),那么就加上这个节点的值
    if (a[root].lc==0&&a[root].rc==0)
        return dp[root][x]=a[root].val;
    //如果这是转播站且没有兄弟节点,那么只能往左子(儿子节点)走
    //因为经过这个节点,所以要加上这个答案。再递归左子树。
    if (a[root].lc!=0&&a[root].rc==0)
        return dp[root][x]=dfs(a[root].lc,x)+a[root].val;
    //如果是一个用户节点,但有右子(兄弟节点)
    //那么分选与不选两种情况考虑
    if (a[root].lc==0&&a[root].rc!=0) {
        //选的话要加上它的值
        int res=dfs(a[root].rc,x-1)+a[root].val;
        //不选的话有限制条件:现在要保留的用户节点数(x)不大于它右子树里的用户节点数
        //即保证它的右子里至少有x个用户节点
        if (a[a[root].rc].num>=x)
            res=maximum(res,dfs(a[root].rc,x)); //取两种情况中的较优情况
        return dp[root][x]=res;
    }
    //否则这是个转播站,且有兄弟节点
    int res=-0x7F7F7F7F;
    //枚举在分给左子树i个用户节点,右子树x-i个用户节点的可能性,取最优
    //特别注意:当不传给这个中转站,只给它的兄弟节点(右子)时,可以不取这个节点的值!
    //故i从1开始,而非0(我因为这个WA了好多次)
    for (int i=1; i<=x; i++) {
        if (a[a[root].lc].num<i) continue; //如果左子树里只有不到i个用户节点,不满足条件
        if (a[a[root].rc].num<x-i) continue; //右子同理
        res=maximum(res,dfs(a[root].lc,i)+dfs(a[root].rc,x-i)+a[root].val);
    }
    //如果全给其兄弟节点时兄弟节点里可以有这么多用户节点(num>=x),则取较优值
    if (a[a[root].rc].num>=x)
        res=maximum(res,dfs(a[root].rc,x));
    return dp[root][x]=res;
}

int main() {
    init();
    for (int i=m; i>=0; i--) { //从m~0逆序枚举给i个用户信号的情况
        int ans=dfs(1,i); //从根节点(1)开始记忆化搜索
        //如果不会亏本,则跳出循环,直接输出答案
        if (ans>=0) {
            printf("%d\n",i);
            break;
        }
    }
    return 0;
}

原文地址:https://www.cnblogs.com/tweetuzki/p/8277743.html

时间: 2024-10-07 05:42:09

洛谷 P1273 【有线电视网】的相关文章

洛谷 P1273 有线电视网

2016-05-31 13:25:45 题目链接: 洛谷 P1273 有线电视网 题目大意: 在一棵给定的带权树上取尽量多的叶子节点,使得sigma(val[选择的叶子节点])-sigma(cost[经过的边])>=0 解法: 树状DP 背包DP DP[i][j]表示i号节点为根的子树中选择了j个叶子节点所得到的最大利润 转移方程 DP[i][j]=max(DP[i][j],DP[i][j-k]+DP[son][k]-cost[son][i]); 需要注意的地方 写初始值的时候要注意除了DP[i

洛谷 P1273 有线电视网(dp)

/* 想了半天没想出状态 自己还是太弱了 QAQ 题目问的是最多供给多少户 一般想法是把这个值定义为状态量 没想出来QAQ....看了看题解的状态 很机智.... f[i][j]表示i的子树 选了j个叶子的最大收益 这样 不亏本就是收益>=0 转移的话 先搜一下这个子树有几个叶子 然后枚举儿子 枚举当前儿子分几个叶子 这里的枚举顺序有套路 从大到小枚举i分几个 从小到大枚举j分几个 这样可以避免 重复选择 注意初始化 */ #include<iostream> #include<c

洛谷1273 有线电视网

本题地址:http://www.luogu.org/problem/show?pid=1273 题目描述 某收费有线电视网计划转播一场重要的足球比赛.他们的转播网和用户终端构成一棵树状结构,这棵树的根结点位于足球比赛的现场,树叶为各个用户终端,其他中转站为该树的内部节点. 从转播站到转播站以及从转播站到所有用户终端的信号传输费用都是已知的,一场转播的总费用等于传输信号的费用总和.     现在每个用户都准备了一笔费用想观看这场精彩的足球比赛,有线电视网有权决定给哪些用户提供信号而不给哪些用户提供

luogu P1273 有线电视网

题目链接 luogu P1273 有线电视网 题解 树形背包 dp[i][j]表示在以i为根的子树中,满足j个客户的需求所能获得的最大收益 代码 #include<cstdio> #include<algorithm> #include<cstring> const int maxn = 3007; //using namespace std; inline int read() { int x = 0,f = 1; char c = getchar(); while(

Luogu P1273 有线电视网(树形dp+背包)

题面 题目描述 某收费有线电视网计划转播一场重要的足球比赛.他们的转播网和用户终端构成一棵树状结构,这棵树的根结点位于足球比赛的现场,树叶为各个用户终端,其他中转站为该树的内部节点. 从转播站到转播站以及从转播站到所有用户终端的信号传输费用都是已知的,一场转播的总费用等于传输信号的费用总和. 现在每个用户都准备了一笔费用想观看这场精彩的足球比赛,有线电视网有权决定给哪些用户提供信号而不给哪些用户提供信号. 写一个程序找出一个方案使得有线电视网在不亏本的情况下使观看转播的用户尽可能多. 输入输出格

[P1273]有线电视网

Link: P1273 传送门 Solution: 比较裸的树形$dp$ 令$dp[i][j]$表示以$i$为根的子树中选$j$个叶子的最小代价 最后找到使得$dp[1][k]\ge 0$的最大$k$即可 Code: #include <bits/stdc++.h> using namespace std; const int MAXN=3005; struct edge{int nxt,to;}e[MAXN<<2]; int n,m,x,y,w[MAXN],sz[MAXN],dp

[P1273] 有线电视网 (树形DP+分组背包)

题意:给出一棵树,有边权,只有叶子节点有点权,求一个合法方案(选择走到哪几个叶子节点,且路径上的权值和 <= 要走到的叶子节点的点权和),使得选择的叶子节点数量尽量的多: 解法:树形DP+分组背包: 1.树形DP:这是一棵树,所以叫树形DP: 2.分组背包:在这里主要是运用到了它的思想:我们可以设 f[i][j],表示 i节点选择了 j个叶子节点的费用最大值:假设现在在 x节点,它的下面有 n个叶子节点(不是它的儿子),那么我们就要处理出它选 1,2,3,……,n 个叶子节点的情况,但是由于这是

洛谷 P2709 BZOJ 3781 小B的询问

题目描述 小B有一个序列,包含N个1~K之间的整数.他一共有M个询问,每个询问给定一个区间[L..R],求Sigma(c(i)^2)的值,其中i的值从1到K,其中c(i)表示数字i在[L..R]中的重复次数.小B请你帮助他回答询问. 输入输出格式 输入格式: 第一行,三个整数N.M.K. 第二行,N个整数,表示小B的序列. 接下来的M行,每行两个整数L.R. 输出格式: M行,每行一个整数,其中第i行的整数表示第i个询问的答案. 输入输出样例 输入样例#1: 6 4 3 1 3 2 1 1 3

洛谷1231 教辅的组成

洛谷1231 教辅的组成 https://www.luogu.org/problem/show?pid=1231 题目背景 滚粗了的HansBug在收拾旧语文书,然而他发现了什么奇妙的东西. 题目描述 蒟蒻HansBug在一本语文书里面发现了一本答案,然而他却明明记得这书应该还包含一份练习题.然而出现在他眼前的书多得数不胜数,其中有书,有答案,有练习册.已知一个完整的书册均应该包含且仅包含一本书.一本练习册和一份答案,然而现在全都乱做了一团.许多书上面的字迹都已经模糊了,然而HansBug还是可