洛谷 P3125 [USACO15OPEN]Bessie的生日自助餐Bessie's… ( spfa+dp) 题解

在FarmerJohn的牧场中,Bessie要在N块草地间穿梭,每块草地有独特的能量值,但只能消费高于已食用的草地质量。求Bessie能积累的最大能量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目来源:

https://www.luogu.org/problemnew/show/P3125

题目描述:

题目描述

For Bessie the cow’s birthday, Farmer John has given her free reign over one

of his best fields to eat grass.

The field is covered in NN patches of grass (1 \le N \le 10001≤N≤1000), conveniently

numbered 1\ldots N1…N, that each have a distinct quality value. If Bessie eats

grass of quality QQ, she gains QQ units of energy. Each patch is connected to

up to 10 neighboring patches via bi-directional paths, and it takes Bessie EE

units of energy to move between adjacent patches (1 \le E \le 1,000,0001≤E≤1,000,000).

Bessie can choose to start grazing in any patch she wishes, and she wants to

stop grazing once she has accumulated a maximum amount of energy.

Unfortunately, Bessie is a picky bovine, and once she eats grass of a certain

quality, she’ll never eat grass at or below that quality level again! She is

still happy to walk through patches without eating their grass; in fact, she

might find it beneficial to walk through a patch of high-quality grass without

eating it, only to return later for a tasty snack.

Please help determine the maximum amount of energy Bessie can accumulate.

为了庆祝奶牛Bessie的生日,Farmer John给了她一块最好的牧场,让她自由的享用。

牧场上一共有N块草地(1≤N≤1000),编号为1...N,每块草地上牧草的质量都不同。

如果Bessie吃掉的草地上牧草质量为Q,她可以获得Q单位的能量。

每块草地最多和10块草地有相连的道路,在相连的两个草地之间走动需要消耗E单位的能量(1≤E≤1,000,000)。

Bessie可以从任意一块草地开始吃草,并且想要在获得了最多能量的时候停止。

有点遗憾的,Bessie是一头挑食的奶牛,一旦她吃过了一定质量的牧草,她就不会再吃相同或更低质量的牧草!但是她仍然很愿意路过某些草地,而不吃它们。实际上,她发现路过一块高质量的草地而不吃它,等一下返回再去享用,有时会更有利!

请帮忙计算Bessie能够获得的能量的最大值。

输入输出格式

输入格式:

 

The first line of input contains NN and EE. Each of the remaining NN lines

describe a patch of grass. They contain two integers QQ and DD giving the

quality of the patch (in the range 1\ldots 1,000,0001…1,000,000) and its number of

neighbors. The remaining DD numbers on the line specify the neighbors.

1行:包含两个整数N和E。

接下来N行:每行描述一块草地,首先两个整数Q和D,分别表示草地上牧草的质量(范围1…1,000,000)和相连的草地数量。然后D个整数表示相连的草地。

 

输出格式:

 

Please output the maximum amount of energy Bessie can accumulate.

输出Bessie能够获得的能量的最大值。

 

输入输出样例

输入样例#1: 复制

5 2
4 1 2
1 3 1 3 4
6 2 2 5
5 2 2 5
2 2 3 4

输出样例#1: 复制

7

说明

Bessie starts at patch 4 gaining 5 units of energy from the grass there. She

then takes the path to patch 5 losing 2 units of energy during her travel.

She refuses to eat the lower quality grass at patch 5 and travels to patch 3

again losing 2 units of energy. Finally she eats the grass at patch 3 gaining 6 units of energy

for a total of 7 energy.

Note tha the sample case above is different from test case 1 when you submit.

感谢@蒟蒻orz神犇 提供翻译

解题思路:

       这题,一开始看没什么思路,后来看了题解,自己推了,一下才知道dp的状态转移方程,设dp【u】是从u出发获得的最大能量,所以dp【u】=max(dp【u】,dp【v】-dis【v】【u】+w【u】),w【u】为u点的能量,dis【v】【u】是v到u的最短距离,首先我们要求出每个点的最短距离,因为本题是稀疏图,所以我们只要用n次spfa会比较快,因为题目要求质量递增,所以我们可以先排个序,保证w【v】<w【u】,只要就行了,具体细节看代码。。

代码:

#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
#include <queue>
#include <stack>
#include <cmath>
#define inf 0x3f3f3f3f
#define ll long long
using namespace std;
const int  maxn=1e5+10;
int n,E,head[1010],cnt=0,vis[1010],dis[1010][1010],dp[1010];
struct newtt
{
    int num,w;
}dian[1010];
bool cmp(newtt a,newtt b)
{
    return a.w<b.w;
}
struct newt
{
    int to,next,cost;
}e[maxn];
void addedge(int u,int v,int w)
{
    e[cnt].to=v;
    e[cnt].next=head[u];
    e[cnt].cost=w;
    head[u]=cnt++;
}
void sfpa(int u)
{
    for(int i=1;i<=n;i++)dis[u][i]=inf,vis[i]=0;
    queue<int>q;
    q.push(u);
    vis[u]=1;
    dis[u][u]=0;
    while(!q.empty())
    {
        int now=q.front();
        q.pop();
        vis[now]=0;
        for(int i=head[now];i!=-1;i=e[i].next)
        {
            int v=e[i].to;
            if(dis[u][v]>dis[u][now]+e[i].cost)
            {
                dis[u][v]=dis[u][now]+e[i].cost;
                if(vis[v])continue;
                q.push(v);
                vis[v]=1;
            }
        }
    }
}
int main()
{
    scanf("%d%d",&n,&E);
    for(int i=1;i<=n;i++)head[i]=-1;
    for(int i=1,m;i<=n;i++)
    {
        scanf("%d",&dian[i].w);
        dp[i]=dian[i].w;
        dian[i].num=i;
        scanf("%d",&m);
        for(int j=1,x;j<=m;j++)
        scanf("%d",&x),addedge(i,x,E);
    }
    sort(dian+1,dian+n+1,cmp);
    for(int i=1;i<=n;i++)sfpa(i);
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<i;j++)
        {
            
            int u=dian[i].num,v=dian[j].num;
        //	printf("%d\n",dis[v][u]);
            dp[u]=max(dp[u],dp[v]-dis[v][u]+dian[i].w);
        }
        ans=max(ans,dp[dian[i].num]);
        //printf("%d %d\n",dian[i].num,dp[dian[i].num]);
    }
    printf("%d\n",ans);
    return 0;
}

 

<think>题目:洛谷P1700 [USACO19OPEN] Milk Factory B 题目描述:有一个牛奶加工厂,有N个站点(编号1到N),站点之间有传送带,传送带是单向的。现在要找一个站点,使得从这个站点出发,能够到达所有其他站点(允许通过传送带间接到达)。如果存在多个这样的站点,则选择编号最小的。如果没有这样的站点,则输出-1。 输入格式: 第一行:整数N(1≤N≤100),表示站点的数量。 接下来N-1行:每行两个整数a和b,表示有一条从a到b的传送带(注意是单向边,方向是a->b)。 输出格式: 输出满足条件的站点编号,如果没有则输出-1。 分析: 题目要求找到一个点,使得从这个点出发可以到达所有其他点。实际上,这就是在图中寻找一个“根”节点,使得从该节点出发可以遍历整个图(注意图是有向的,且边数为N-1,所以图是树?但注意:题目中并没有保证是树,因为可能有多个父节点?但题目只给了N-1条边,所以实际上是一个有向图,且边数比节点数少1,因此它可能是一个有向无环图(DAG)或者有环?但题目没有说,不过N-1条边,如果有环则无法形成连通图(因为树是无环的),但这里是有向图,所以可能形成树或者森林?然而题目要求能够到达所有其他站点,所以图必须是连通的(弱连通?)。 注意:题目中只给出了N-1条边,所以整个图是一个有向图,且边数比节点数少1,那么它可能是一棵有向树(但方向不一定是从根向叶子,因为题目输入是a->b,所以可能是从a指向b)。但是,题目没有保证边的方向是从根向叶子,所以我们需要考虑。 由于图只有N-1条边,所以整个图可能是一个有向无环图(DAG)并且是一棵树(即基图是一棵树,但方向任意)。但注意:树中边的方向通常是从根向叶子,但这里可能反过来?所以我们需要判断。 然而,题目要求的是:存在一个点,从它出发可以到达所有其他点。那么我们可以尝试对每一个点进行DFS/BFS,检查从这个点出发能否遍历所有节点。因为N最大为100,所以对每个点做一次DFS/BFS,总时间复杂度为O(N*(N+E)) = O(100*(100+99)) ≈ 100*200 = 20000,可以接受。 步骤: 1. 读入N。 2. 构建图:使用邻接表。 3. 对于每个节点i(从1到N),执行BFS/DFS,记录从i出发能够访问到的节点数。 4. 如果能够访问到所有N个节点(包括自己),则记录这个节点。 5. 在记录的所有节点中,选择编号最小的输出;如果没有,输出-1。 注意:图是有向的,所以边a->b,只能从a到b,不能反向。 代码实现: 我们将使用BFS,因为BFS使用队列,非递归,且对于100个节点来说很安全。 具体步骤: 1. 读入N,然后读入N-1行,每行两个整数a和b,构建有向边:graph[a].push_back(b) (注意:题目中传送带方向是a->b)。 2. 对于每个节点i(1~N): 初始化visited数组(大小为N+1,全部为false),初始化队列,将i入队,标记visited[i]=true。 然后进行BFS:每次从队列中取出一个节点u,遍历u的所有邻居v,如果v没有被访问过,则标记并加入队列。 统计访问过的节点数(count)。 3. 如果count等于N,则说明节点i满足条件。 4. 遍历完所有节点后,如果存在满足条件的节点,输出最小的节点编号;否则输出-1。 注意:可能存在多个满足条件的节点,我们只需要最小的那个,所以一旦找到一个满足条件的节点,我们可以记录它的编号,然后继续找,最后取最小的(或者我们按节点编号从小到大遍历,那么第一个满足条件的节点就是最小的,但题目要求多个时取最小,所以我们可以按节点编号从小到大遍历,找到第一个满足条件的就输出并退出?不行,因为可能有多个,但我们需要最小的,所以即使找到一个,后面可能有更小的?不对,节点编号是从1到N,我们从小到大遍历,所以第一个满足条件的节点就是最小的节点?不对,因为节点编号是1,2,...,N,我们按节点编号顺序遍历,那么第一个满足条件的节点就是最小的节点吗?但是,我们遍历的顺序是1,2,...,N,所以当我们找到第一个满足条件的节点时,它一定是编号最小的(因为编号小的先遍历)。所以我们可以这样:从1到N遍历,一旦找到一个节点满足条件,就记录答案并跳出循环?不行,因为可能节点1不满足,节点2满足,但节点3也满足,而节点2比节点3小,所以我们应该记录所有满足条件的节点?然后取最小值?或者因为我们是按从小到大的顺序遍历,所以第一个满足条件的节点就是最小的?不对,因为节点1满足条件,那么它就是最小的;节点1不满足,节点2满足,那么节点2就是当前最小的;节点3满足条件但是比节点2大,所以我们在遍历到节点2时就可以记录并继续找更小的?不对,因为节点编号是递增的,后面不会出现比2更小的节点了。所以当我们按节点编号从小到大遍历时,第一个满足条件的节点就是最小的节点,可以直接输出并结束?不对,因为可能节点1不满足,节点2满足,然后节点3也满足,但是节点2比节点3小,所以我们在节点2满足时就可以输出节点2并结束循环(因为后面节点编号都比2大,所以最小的就是2)。因此,我们可以按节点编号从小到大遍历,一旦遇到一个满足条件的节点,就输出它并结束程序(因为后面即使有满足条件的节点,编号也更大,所以第一个满足条件的节点就是最小的)。如果没有遇到,最后输出-1。 但是,如果我们从1到N遍历,那么第一个满足条件的节点就是最小的,所以我们可以: for (int i=1; i<=N; i++) { 进行BFS,统计从i出发能到达的节点数count if (count == N) { cout << i << endl; return 0; // 结束程序 } } // 如果循环结束还没找到 cout << -1 << endl; 这样是可行的。 但是,注意:题目要求是“如果存在多个这样的站点,则选择编号最小的”,而我们的遍历顺序是1到N,所以第一个找到的满足条件的节点就是最小的,然后直接输出并结束程序。 代码实现: 注意:图可能有环吗?题目中只有N-1条边,所以不可能有环(因为环的存在需要至少与节点数相同的边数,比如一个环需要n条边,而这里只有n-1条边),所以图是无环的,因此不会出现重复访问的问题(但我们的BFS已经用visited数组避免重复访问了,所以即使有环也没关系)。 但是,图可能是不连通的吗?题目要求能够到达所有站点,所以如果图不连通,那么任何一个节点出发都不可能到达所有节点。所以我们的BFS在某个节点开始,如果访问的节点数不足N,就跳过。 由于边数只有N-1,所以整个图可能是一个有向树(基图是无向树,但边有方向)。但是,方向可能不是从根向叶子,所以可能有些节点有多个入度?题目没有说,所以按照输入处理。 我们按照上述思路编写代码。 注意:输入是N-1行,每行两个整数a和b,表示a->b的边。 代码示例:</think>### 解法思路 对于洛谷P1700 [USACO19OPEN] Milk Factory B 问题,需要找到一个站点(节点),使得从该站点出发能通过单向传送带到达所有其他站点。由于图只有 N-1 条边(树形结构),可能存在多个解,需输出编号最小的解,若无解则输出 -1。 #### 关键点 1. **图建模**:将站点视为节点,传送带视为有向边(a→b)。 2. **连通性检查**:对每个节点进行 BFS/DFS,检查是否能访问所有节点。 3. **最优选择**:从小到大遍历节点,找到第一个满足条件的节点即可(因编号递增)。 ### C++ 代码实现 ```cpp #include <iostream> #include <vector> #include <queue> #include <cstring> using namespace std; int main() { int N; cin >> N; vector<vector<int>> graph(N + 1); // 邻接表(索引0不使用) // 构建有向图(注意:共N-1条边) for (int i = 0; i < N - 1; i++) { int a, b; cin >> a >> b; graph[a].push_back(b); // 添加边 a→b } // 检查每个节点作为起点 for (int start = 1; start <= N; start++) { vector<bool> visited(N + 1, false); // 访问标记数组 queue<int> q; int count = 0; // 统计可达节点数 // BFS初始化 q.push(start); visited[start] = true; while (!q.empty()) { int u = q.front(); q.pop(); count++; // 遍历当前节点的所有出边 for (int v : graph[u]) { if (!visited[v]) { visited[v] = true; q.push(v); } } } // 若可达所有节点,输出并结束 if (count == N) { cout << start << endl; return 0; } } // 无解情况 cout << -1 << endl; return 0; } ``` ### 代码解释 1. **输入处理**: - 读取站点数 `N`。 - 构建邻接表 `graph`,存储每条有向边(`a→b` 对应 `graph[a].push_back(b)`)。 2. **BFS遍历**: - 对每个节点 `start` (1 到 N) 进行 BFS: - 初始化 `visited` 数组记录访问状态。 - 使用队列 `q` 进行广度优先搜索,统计可达节点数 `count`。 - 若 `count == N`,说明从 `start` 可达所有节点,输出 `start` 并结束程序。 3. **无解处理**: - 若遍历完所有节点均未找到解,输出 `-1`。 ### 复杂度分析 - **时间复杂度**:O(),最坏情况下对每个节点进行 BFS(每个节点遍历所有边)。 - **空间复杂度**:O(N),用于存储邻接表和访问标记。 ### 相关问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值