洛谷P1273 有线电视网

有线电视网
本题地址: http://www.luogu.org/problem/show?pid=1273

题目描述

某收费有线电视网计划转播一场重要的足球比赛。他们的转播网和用户终端构成一棵树状结构,这棵树的根结点位于足球比赛的现场,树叶为各个用户终端,其他中转站为该树的内部节点。
从转播站到转播站以及从转播站到所有用户终端的信号传输费用都是已知的,一场转播的总费用等于传输信号的费用总和。
现在每个用户都准备了一笔费用想观看这场精彩的足球比赛,有线电视网有权决定给哪些用户提供信号而不给哪些用户提供信号。
写一个程序找出一个方案使得有线电视网在不亏本的情况下使观看转播的用户尽可能多。
输入输出格式
输入格式:
输入文件的第一行包含两个用空格隔开的整数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的费用。最后一行依次表示所有用户为观看比赛而准备支付的钱数。

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

输入输出样例

输入样例#1:

5 3
2 2 2 5 3
2 3 2 4 3
3 4 2
输出样例#1:
2

说明

样例解释
如图所示,共有五个结点。结点①为根结点,即现场直播站,②为一个中转站,③④⑤为用户端,共M个,编号从N-M+1到N,他们为观看比赛分别准备的钱数为3、4、2,从结点①可以传送信号到结点②,费用为2,也可以传送信号到结点⑤,费用为3(第二行数据所示),从结点②可以传输信号到结点③,费用为2。也可传输信号到结点④,费用为3(第三行数据所示),如果要让所有用户(③④⑤)都能看上比赛,则信号传输的总费用为:
2+3+2+3=10,大于用户愿意支付的总费用3+4+2=9,有线电视网就亏本了,而只让③④两个用户看比赛就不亏本了。

卧槽初看这道题整个人都是懵的 这个边可以重复利用但是只付一次的费用 状态又不可能表示哪些边选了 = =怎么搞啊 后来发现我制杖了 既然每条边不用重复计算 那就先算出对于每个点在他的后代中选几个点的费用 再计算父亲的时候利用儿子的费用再加上 它与儿子的边权 就好了
那么状态就成形了 dp[i][j]表示i节点在其后代中选了j个的最大收入
更新的话 把每个儿子扫一遍 并用当前搜到的儿子选了的用户数更新状态
即dp[i][j]=max(dp[i][j],dp[i][j-k]+dp[v][k]-w)
跟01背包是差不多嘛
其余的没什么看代码就好了


#include<cstdio> 
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=3000+10;

int n,m,first[maxn],next[maxn],w[maxn],v[maxn],money[maxn];
int cnt=0;
void add_edge(int uu,int vv,int ww)
{
          v[++cnt]=vv;
          w[cnt]=ww;
          next[cnt]=first[uu];
          first[uu]=cnt;
}

void init_data()
{
          cin>>n>>m;
          for(int i=1;i<=n;i++)
              first[i]=-1;
          for(int u=1,son;u<=n-m;u++)
          {
                    scanf("%d",&son);
                    for(int j=1,v,w;j<=son;j++)
                    {
                              scanf("%d%d",&v,&w);
                              add_edge(u,v,w);
                    }
          }
          for(int u=n-m+1;u<=n;u++)scanf("%d",money+u);
}

int dp[maxn][maxn];
int f(int u)///返回的是当前节点儿子中有多少个用户
{
          if(u>n-m)
          {
                    dp[u][1]=money[u];
                    return 1;
          }
          int sum=0;
          for(int i=first[u];i!=-1;i=next[i])
          {
                    int x=f(v[i]);sum+=x;
                    for(int j=sum;j;j--)          ///注意是倒循环
                         for(int k=1;k<=x;k++)///儿子最多选它后代中用户个数
                               dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v[i]][k]-w[i]);
          }
          return sum;
}

int main()
{
          init_data();
          memset(dp,-60,sizeof(dp));
          for(int u=1;u<=n;u++)
               dp[u][0]=0;
          f(1);
          int i=m;
          for(;i;i--) if(dp[1][i]>=0) break;
          printf("%d",i);
          return 0;
} 
### 关于 P7731 的题目解析 目前尚未找到针对 P7731 的具体官方题解或权威资料。然而,基于常见的编程竞赛问题模式以及已知的相关算法思路[^1],可以推测该类问题可能涉及动态规划、贪心策略或其他经典算法模型。 以下是关于此类问题的一般性分析框架: #### 动态规划的应用场景 如果 P7731 是一道典型的动态规划问题,则其核心在于状态定义与转移方程的设计。例如,在某些货币兑换问题中,可以通过维护两个数组分别表示当前持有的不同币种的最大价值[^2]。这种设计方式类似于引用中的美元汇率转换逻辑。 ```cpp #include <iostream> using namespace std; int main(){ int n; cin >> n; double dp1 = 100.0, dp2 = 0; // 初始条件设定 for(int i=1;i<=n;i++){ int a; cin >> a; double tmp = dp1; dp1 = max(dp1, dp2 / a * 100); // 更新美元最大值 dp2 = max(dp2, tmp / 100 * a); // 更新马克最大值 } cout << fixed << setprecision(2) << dp1; // 输出最终结果 } ``` 上述代码片段展示了如何通过迭代更新来解决多阶段决策优化问题。 #### 贪心算法的可能性 对于另一些特定类型的题目,采用贪心方法可能会更加高效。比如当输入数据满足某种单调性质时,可以直接选取局部最优解从而达到全局最佳效果。 ```cpp #include <bits/stdc++.h> using namespace std; struct Item { long long w,v; }item[1000]; bool cmp(Item x,Item y){ return (double)x.v/x.w>(double)y.v/y.w; } int main(){ int N,W; cin>>N>>W; for(int i=0;i<N;i++)cin>>item[i].w>>item[i].v; sort(item,item+N,cmp); double res=0; for(int i=0;i<N && W>0;i++){ if(W>=item[i].w){res+=item[i].v;W-=item[i].w;} else{res+=(double)item[i].v/(double)item[i].w*(double)W;W=0;} } printf("%.2f\n",res); } ``` 此段程序体现了利用物品单位重量价值排序后依次装载的过程。 ### 总结 尽管未能直接获取到 P7731 的确切解答方案,但从已有经验出发仍可构建合理的求解路径。无论是运用动态规划还是尝试其他技巧如模拟、枚举等均需紧密结合实际需求展开深入探讨。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值