USACO 1.4 Mother's Milk 母亲的牛奶(经典的dfs倒水问题)

解决USACO比赛中的倒水问题,通过深度优先搜索算法帮助农民约翰找出当A桶是空的时候,C桶中牛奶所剩量的所有可能性。

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

【USACO1.4.4】Mother's Milk 母亲的牛奶

Time Limit:10000MS  Memory Limit:65536K
Total Submit:42 Accepted:27 
Case Time Limit:1000MS

Description

农民约翰有三个容量分别是A,B,C升的桶,A,B,C分别是三个从1到20的整数, 最初,A和B桶都是空的,而C桶是装满牛奶的。有时,农民把牛奶从一个桶倒到 另一个桶中,直到被灌桶装满或原桶空了。当然每一次灌注都是完全的。由于节约, 牛奶不会有丢失。 
写一个程序去帮助农民找出当A桶是空的时候,C桶中牛奶所剩量的所有可能性。

Input

单独的一行包括三个整数A,B和C。

Output

只有一行,升序地列出当A桶是空的时候,C桶牛奶所剩量的所有可能性。

Sample Input

样例输入1:
8 9 10

样例输入2:
2 5 10

Sample Output

样例输出1:
1 2 8 9 10

样例输出2:
5 6 7 8 9 10

解题思路:

这道题其实一道经典的dfs倒水问题,说的就是两种倒水的情况,,直到被灌桶装满或原桶空了,一定要好好理解这句话的含义,我一开始就卡在这句话上,想到8 9 10的样例为 1 2 3 4 5 6 7 8 9 10,,,,真是SB啊。

好了,来说说倒水吧,就根据被灌桶装满和原桶为空两种情况来进行dfs就好,如果被刀捅(以后称为B桶),原桶(以后称为A桶).

如果A桶满了,但是B桶还有奶,那么这个时候的状态和B桶空了,A桶还没满是不同的。

针对这道题,由于只有3个桶,且前两个桶是空的,后一个桶是满的,那么定义(0,0,C)作为其实的状态,其实开始的时候C==c,比如拿C桶来说吧,C桶中的奶的情况数,

只可能有0,1,2,3,4,5,,,C这C+1种情况,所以我们只需要开一个record[C]的数组去存放我们需要观察的结果.

然后就是dfs了,怎么dfs呢?其实3个桶只有6种情况,也就是说c->a,c->b,a->c,a->b,b->a,b->c,依次枚举出这6种情况的dfs方案,没每次的倒奶中,又要考虑上面说的两种情况。。。。详细的就看代码吧,我第一少写了个状态,,一运行就蹦~

代码:

 /*
 ID:wikioi_2
 PROG: milk3
 LANG: C++
 */
# include<cstdio>
# include<iostream>
# include<cmath>

using namespace std;

# define MAX 21

int A,B,C;
int rec[MAX];
int vis[MAX][MAX][MAX];

void dfs( int a,int b,int c )
{
     if ( vis[a][b][c] == 1 )
    {
        return;
    }
    else
    {
        vis[a][b][c] = 1;
    }

    if ( a==0&&!rec[c] )
    {
        rec[c] = 1;
    }


    //c->a
    if ( c >= A-a )
    {
        dfs(A,b,c-A+a);
    }
    else
    {
        dfs(a+c,b,0);
    }
    //c->b
    if ( c >= B-b )
    {
        dfs(a,B,c-B+b);
    }
    else
    {
        dfs(a,b+c,0);
    }
    //b->a
    if ( b >= A-a )
    {
        dfs(A,b-A+a,c);
    }
    else
    {
        dfs(a+b,0,c);
    }
    //b->c
    if ( b >= C-c )
    {
        dfs( a,b-C+c,C);
    }
    else
    {
        dfs( a,0,c+b );
    }
    //a->b
    if ( a >= B-b )
    {
        dfs(a-B+b,B,c);
    }
    else
    {
        dfs(0,b+a,c);
    }
    //a->c
    if ( a >= C-c )
    {
        dfs(a-C+c,b,C);
    }
    else
    {
        dfs(0,b,c+a);
    }

}


int main(void)
{
    freopen("milk3.in","r",stdin);
    freopen("milk3.out","w",stdout);
    //int A,B,C;
    cin>>A>>B>>C;
    dfs(0,0,C);
    int cnt = 0;
    for ( int i = 0;i <= C;i++ )
    {
       if ( rec[i] )
       {
           cnt++;
           if( cnt == 1 )
           {
               cout<<i;
           }
           else
           {
               cout<<" "<<i;
           }
       }
    }
    cout<<endl;


    return 0;
}


<think>题目:P1700 [USACO19OPEN] Milk Factory B 题目描述:有N个站点(编号1~N)和N-1条单向通道,每条通道连接两个站点,且每条通道的牛奶只能单向流动。问是否存在一个站点,使得所有其他站点都可以通过一些通道到达该站点(即该站点是所有站点的汇点)。如果有多个这样的站点,输出编号最小的;如果没有,输出-1。 分析:题目要求找到一个汇点,即所有其他点都能到达的点。由于图中有N-1条单向边,整个图可以看作是一个有向图,且边数比点数少1,因此图可能是一个有向树(或森林)。但题目要求一个点能被所有其他点到达,所以这个点必须是唯一的根(因为边是单向的,且只有N-1条边,所以整个图应该是一个有根树,且根只有一个,否则无法满足所有点都能到达同一个点)。 然而,题目并没有保证图是连通的,但是边数只有N-1条,所以如果图是连通的,那么它是一棵树(有向树)。但是,题目要求的是所有点都能到达同一个点,那么在这个有向树中,这个点必须是根,并且所有边的方向都指向根?实际上,在有向树中,根是唯一没有出边的点,其他所有点都直接或间接指向根。 但是,题目中的边是单向的,而且并没有说方向是指向根还是远离根。所以我们需要重新考虑:题目要求所有点都能到达某个点S,那么S就是汇点。在图中,如果存在这样的点,那么从任意点出发,沿着边的方向都能走到S。因此,S的入度可以是任意的,但其他点必须至少有一条出边(除了S自己,S可以没有出边,但题目没有要求S的出边)。 实际上,我们只需要判断是否存在一个点,使得其他所有点都能到达它。我们可以考虑使用反向图:即把所有的边反向,然后在反向图中,如果存在一个点S,使得从S出发可以到达所有其他点,那么原图中所有点都能到达S。于是问题转化为:在反向图中,是否存在一个点,使得从该点出发可以遍历整个图?如果有多个,取编号最小的。 注意:题目中图可能有多个连通分量吗?题目没有明确说明,但边数只有N-1条,所以如果图是连通的,那么反向图也是连通的。但是,题目并没有保证图是连通的。所以我们需要考虑图不连通的情况。如果图不连通,那么肯定不存在一个点使得所有其他点都能到达它(因为存在两个连通分量,其中一个连通分量内的点无法到达另一个连通分量内的点)。因此,我们首先需要判断反向图是否连通。如果不连通,则输出-1;如果连通,则在反向图中,入度为0的点就是我们要找的点(因为反向图中,入度为0的点在原图中就是出度为0的点,即汇点,但这里我们是在反向图中找起点,所以反向图中入度为0的点就是原图中出度为0的点,而原图中出度为0的点最多只能有一个,因为如果有两个,那么这两个点之间无法互相到达,所以不可能同时被其他点到达。因此,在反向图中,入度为0的点只能有一个,否则就不存在满足条件的点)。 具体步骤: 1. 读取N,然后N-1条边,每条边是a->b(表示从a到b有一条单向边)。 2. 构建反向图:即对于每条边a->b,在反向图中添加一条边b->a。 3. 在反向图中,统计每个点的入度(即原图中每个点的出度)。 4. 在反向图中,入度为0的点(即原图中出度为0的点)应该恰好有一个。因为如果有多个入度为0的点,那么这些点之间无法互相到达(在反向图中,这些点都是起点,没有边指向它们,所以它们之间不可达),所以整个图就不连通。如果只有一个入度为0的点,那么从这个点出发进行BFS/DFS,看是否能遍历所有点(即判断反向图是否连通)。 5. 如果反向图连通且只有一个入度为0的点,那么这个点就是答案(在原图中,这个点就是汇点)。如果有多个入度为0的点,则输出-1(因为反向图不连通,所以原图也不存在一个汇点使得所有点都能到达它)。如果没有入度为0的点?不可能,因为图有N个点,N-1条边,所以一定有入度为0的点(否则成环,但边数不够成环,所以一定有入度为0的点,且至少一个)。 但是,注意:反向图可能有多个入度为0的点,那就说明有多个连通分量(每个连通分量至少有一个入度为0的点),所以图不连通,因此不存在一个汇点。 所以算法: 1. 构建反向图(同时记录反向图的入度,反向图的入度就是原图的出度)。 2. 统计反向图中入度为0的点的个数: - 如果个数为0:不可能(因为N个点,N-1条边,不可能没有入度为0的点)。 - 如果个数大于1:则输出-1(因为多个连通分量)。 - 如果个数为1:那么从该点出发进行BFS/DFS遍历整个反向图,如果遍历到的节点数等于N,则这个点就是答案;否则,图不连通,输出-1。 注意:题目要求编号最小的,但是如果有多个入度为0的点,直接输出-1;如果只有一个,那么那个点就是唯一的候选,所以直接输出它。但是,在反向图中,入度为0的点只有一个,所以不需要比较编号大小(只有一个点)。但是,如果存在多个连通分量,那么入度为0的点不止一个,我们直接输出-1。 所以,我们只需要: - 统计反向图中入度为0的点的个数和具体点。 - 如果个数不为1,则输出-1。 - 如果为1,则从该点开始遍历,如果遍历的节点数等于N,则输出该点;否则输出-1。 但是,还有一种情况:反向图可能有多个连通分量,但我们只统计了一个入度为0的点?不会,因为每个连通分量至少有一个入度为0的点。所以如果入度为0的点只有一个,那么整个反向图只有一个连通分量,那么从该点出发应该能遍历所有点(因为是连通图)。所以实际上,我们只需要判断入度为0的点是否只有一个,然后遍历验证连通性。但理论上,在树中(N个点,N-1条边),如果只有一个入度为0的点,那么它一定是连通的(因为边数正好是N-1,所以连通图就是树)。所以我们可以不进行遍历,但题目没有保证没有环?注意题目中边是单向的,而且只有N-1条边,所以不可能有环(因为环至少需要n条边)。所以反向图是一个有向无环图,且边数为N-1。如果只有一个入度为0的点,那么整个图就是一棵树,所以连通。因此,我们不需要再遍历验证连通性,因为如果入度为0的点只有一个,那么整个图就是连通的。 证明:在反向图中,有N个点,N-1条边,且只有一个入度为0的点(根),那么其他点的入度至少为1(因为边只有N-1条,所以除了根以外,每个点恰好有一个入边,所以整个图构成一棵树)。因此,连通。 所以,步骤简化: 1. 构建反向图,并记录每个点的入度(反向图的入度)。 2. 统计入度为0的点的个数: - 如果个数为1,则输出这个点; - 否则,输出-1。 但是,注意:在反向图中,入度为0的点就是原图中出度为0的点。而原图中,汇点应该是出度为0的点(因为所有其他点都能到达它,它自己不需要有出边)。所以原图中出度为0的点只能有一个,因为如果有两个,那么这两个点之间无法互相到达(即不能同时作为汇点),所以不可能同时存在两个汇点。因此,原图中出度为0的点有且只有一个,那么反向图中入度为0的点有且只有一个。 所以,我们只需要在反向图中统计入度为0的点(即原图中出度为0的点)的个数: - 如果恰好一个,输出这个点; - 否则(0个或多个),输出-1。 但是,原图中出度为0的点不可能为0个吗?因为图有N个点,N-1条边,总出度之和为N-1,所以平均每个点出度不到1,所以一定存在出度为0的点(因为如果所有点出度至少为1,则总出度至少为N,矛盾)。所以出度为0的点至少有一个。因此,我们只需要判断出度为0的点的个数:如果为1,则输出该点;如果大于1,则输出-1。 因此,算法可以更简单:不需要构建反向图,只需要在原图中统计每个点的出度。 - 遍历所有边,对于每条边a->b,那么a的出度加1(因为a指向b)。 - 最后,统计出度为0的点的个数: - 如果个数为1,则输出那个点的编号; - 如果个数大于1,则输出-1。 为什么?因为原图中,如果一个点出度为0,那么它没有出边,所以其他点必须能够到达它。但是,如果有多个出度为0的点,那么这些点之间无法互相到达(因为没有出边,所以不能到达其他点),所以不可能有一个汇点使得所有点都能到达(因为两个出度为0的点,除了自己,另一个出度为0的点无法到达它)。所以只能有一个出度为0的点。而且,这个点就是汇点。 但是,这样够吗?考虑一个例子:3个点,两条边:1->2, 3->2。那么点2的出度为0,点1和点3的出度都为1。那么出度为0的点只有2。所以输出2。而且确实,1和3都能到达2。 再考虑一个例子:3个点,两条边:1->2, 2->3。那么点3出度为0,点1和2的出度分别为11。输出3。而且1和2都能到达3。 再考虑一个反例:3个点,两条边:1->2, 1->3。那么点1的出度为2,点2和3的出度为0。那么出度为0的点有两个(2和3)。所以输出-1。正确,因为2和3之间不能互相到达,1能到达2和3,但是2不能到达3,3不能到达2,所以没有汇点。 但是,还有一个问题:如果存在一个点,它出度不为0,但是其他点无法到达它,那么它就不可能是汇点。而我们要求所有点都能到达汇点。在上面的简单统计中,我们只关注了出度为0的点,因为只有出度为0的点才可能是汇点(因为如果有出边,那么它就可以到达其他点,那么其他点就不能通过它作为终点,因为要求终点是汇点,即终点不能有出边到达其他点,所以汇点必须出度为0)。所以汇点必须是出度为0的点。 那么,如果只有一个出度为0的点,是否一定所有点都能到达它?考虑下面例子:4个点,边为:1->2, 2->3, 3->2。注意,这里出现了环:2->3->2,然后还有一条边1->2。那么点1的出度是1,点2的出度是1(因为2->3),点3的出度是1(3->2),点4的出度?这里没有点4,所以是3个点?我们重新设计:3个点,边:1->2, 2->3, 3->2。那么点1的出度1,点2的出度1,点3的出度1。那么没有出度为0的点?但是图有3个点,2条边?不对,这里我们写了3条边。所以题目只有N-1条边,所以这里边数应该是2条。所以重新设计:3个点,2条边:1->2, 3->2(这样就没有环了,且出度为0的点只有2)。所以题目中不会出现环,因为边数只有N-1条,所以不可能形成环(环至少需要N条边?不对,环至少需要n条边?对于n个点,环至少需要n条边?不对,环可以只有3条边(3个点形成一个三角环),但这里只有2条边,所以不可能形成环。所以图是无环的,因为边数不够。 因此,图是一个有向无环图(DAG),且边数N-1。所以整个图可能是一个有向森林(多个连通分量)或者一棵树。每个连通分量是一棵有向树(可能有多个根,即多个入度为0的点,但在原图中,我们关心的是出度为0的点)。 因此,结论:我们只需要统计原图中出度为0的点的个数。如果个数为1,则输出该点;否则输出-1。 但是,考虑连通性:如果图不连通,那么可能会有多个出度为0的点(每个连通分量至少有一个出度为0的点),所以输出-1。如果连通,那么整个图是一棵树,那么出度为0的点只能有一个(即叶子节点,但树中可能有多个叶子节点?注意:有向树中,叶子节点是出度为0的点。但是,在一棵有向树中,叶子节点可以有多个,但根只有一个(入度为0)。而这里我们要求的是汇点,汇点必须是所有点都能到达的点,所以汇点必须是树根?不对,在有向树中,边是从根指向叶子。那么所有点都能到达叶子节点吗?不一定,因为只能从根到叶子,而叶子节点之间不能互相到达。所以汇点应该是叶子节点?不对,应该是根节点?不对,因为根节点只能到达其他点,而其他点不能到达根节点(因为边是根指向叶子)。所以方向反了。 所以,我们之前用反向图的思想是正确的。所以还是应该用反向图。 重新思考:原图中,要求所有点都能到达汇点S。那么,在反向图中,我们要求从S出发能到达所有点(因为反向图中,原图的边反向,所以原图中到达S的路径在反向图中就是从S出发的路径)。因此,在反向图中,S必须是一个根节点(入度为0),并且从S出发能遍历所有点(即反向图连通)。所以反向图中,入度为0的点(即原图中出度为0的点)只能有一个,且这个点就是我们要找的S。但是,反向图是否连通?如果反向图连通,那么S就能到达所有点。所以我们需要判断反向图的连通性。 但是,由于边数只有N-1,所以如果反向图中入度为0的点只有一个,那么整个反向图就是一棵以该点为根的有向树,所以连通。因此,我们只需要判断反向图中入度为0的点是否只有一个。 所以,算法: 1. 读入N,然后N-1条边。 2. 初始化一个数组outdeg(原图的出度)或者用indeg_rev(反向图的入度)来统计。实际上,我们统计反向图的入度:即每个点作为终点被指向的次数(也就是原图中每个点的入度?不对,反向图:原边a->b,反向图是b->a,所以反向图中,b的入度是指有多少条边指向b?不对,在反向图中,我们关心的是每个点在反向图中的入度(即原图中该点的出度)。所以,我们可以直接统计原图中每个点的出度:对于每条边a->b,那么a的出度加1(表示a有一条出边)。 所以,实际上,我们不需要显式构建反向图,只需要统计原图中每个点的出度(因为反向图的入度就是原图中该点的出度)。然后,我们找原图中出度为0的点(即反向图中入度为0的点)的个数: - 如果个数为1,则输出那个点; - 否则,输出-1。 所以,代码步骤: 1. 初始化一个数组outdegree,长度为N+1(下标1~N),初始为0。 2. 读入N-1条边,对于每条边(a, b): outdegree[a]++ (因为a指向b,所以a的出度加1) (注意:b的入度我们不需要,因为我们不关心入度) 3. 统计outdegree数组中,值为0的点的个数和具体点。 - 如果个数为0:不可能(因为总出度之和为N-1,所以至少有一个点出度为0)。 - 如果个数为1:则输出那个点的编号。 - 如果个数大于1:输出-1。 但是,考虑一个特殊情况:只有一个点(N=1)。那么没有边(N-1=0),所以该点的出度为0。所以个数为1,输出1。 所以,这个算法可以处理所有情况。 验证: 例子1:N=3,边:1->2, 3->2。那么: outdegree[1]=1, outdegree[3]=1, outdegree[2]=0。所以出度为0的点只有2,输出2。 例子2:N=3,边:1->2, 2->3。那么: outdegree[1]=1, outdegree[2]=1, outdegree[3]=0。输出3。 例子3:N=3,边:1->2, 1->3。那么: outdegree[1]=2, outdegree[2]=0, outdegree[3]=0。出度为0的点有两个(2和3),输出-1。 因此,代码实现: 注意:输入格式:第一行一个整数N,接下来N-1行,每行两个整数a,b,表示一条从a到b的单向边。 代码: ```python n = int(input().strip()) outdegree = [0] * (n+1) # 0号位置不用,从1到n for i in range(n-1): a, b = map(int, input().split()) outdegree[a] += 1 # 统计出度为0的点 candidates = [] for i in range(1, n+1): if outdegree[i] == 0: candidates.append(i) if len(candidates) == 1: print(candidates[0]) else: print(-1) ``` 但是,题目要求输出编号最小的?这里candidates可能有多个,但我们在统计的时候,如果个数为1,那么只有一个候选;如果个数大于1,输出-1。所以不需要比较大小。 但是,如果题目要求如果有多个候选,输出编号最小的?但是题目要求:如果有多个这样的站点,输出编号最小的;如果没有,输出-1。而我们的算法中,当有多个出度为0的点时,我们输出-1,因为不可能有汇点。所以符合题意。 因此,代码如上。 但是,我们是否漏掉了什么?考虑图不连通的情况,比如两个连通分量,每个连通分量都有一个出度为0的点。那么我们就输出-1,正确。 但是,还有一种情况:如果图是连通的,但是有多个出度为0的点?不可能,因为连通的有向图(树)只能有一个根(入度为0),但出度为0的点(叶子)可以有多个。但是,我们要求的是汇点,汇点必须出度为0,而且所有点都能到达它。在树中,只有叶子节点出度为0,但是叶子节点可能不止一个,而且除了根节点,其他节点不能到达根节点(如果边是从根指向叶子)。所以方向反了。 所以,我们之前用反向图的思想,实际上应该统计反向图的入度(即原图的出度)为0的点,并且要求只有一个。但是,在树中,叶子节点可以有多个,所以如果原图是一棵正常的树(边从根指向叶子),那么原图中出度为0的点(叶子)有多个,所以输出-1。但是,题目要求的汇点应该是叶子节点吗?不,汇点应该是所有点都能到达的点,所以应该是叶子节点。但是,多个叶子节点之间不能互相到达,所以不可能有一个汇点使得所有点都能到达它。所以输出-1正确。 那么,什么时候会有汇点?当图是一棵反向树:即所有边都指向根节点(即根节点是汇点)。那么原图中,根节点的出度应该是0(因为根节点没有出边,其他点都有出边指向根节点)。所以原图中,根节点出度为0,其他点出度为1。所以出度为0的点只有根节点一个。因此,这种情况符合我们的算法。 所以,算法正确。 因此,我们只需要按照上述代码实现。 但是,题目要求使用中文回答问题,并且在最后输出3-5个相关问题。 所以,最终回答: 我们使用统计原图中每个点的出度的方法,如果出度为0的点恰好有一个,则输出该点;否则输出-1。代码实现如上。 相关问题: §§相关问题§§ 1. 如果题目要求的是源点(即从该点可以到达所有其他点)而不是汇点,应该如何修改算法? 2. 如果图中存在环,且边数可能大于N-1,那么如何寻找汇点(即所有其他点都能到达的点)? 3. 在本题中,为什么只需要统计出度为0的点的个数而不需要检查连通性? 4. 如果题目要求输出所有可能的汇点(如果有多个),并按照编号升序输出,应该如何修改算法? 5. 如果图是无向图,那么如何找到所有点都能到达的点(即中心点)?</think>### 题目分析 题目要求在有向图中找到一个汇点(所有其他点都能到达的点)。图中有 $N$ 个节点(编号 $1$ 到 $N$)和 $N-1$ 条单向边。关键点: 1. **汇点特性**:汇点必须是出度为 $0$ 的点(因为它没有出边,其他点必须能到达它)。 2. **唯一性**:若存在多个出度为 $0$ 的点,则它们互相不可达,无法同时作为汇点,此时无解。 3. **连通性**:边数 $N-1$ 保证图中无环,若出度为 $0$ 的点恰好有一个,则整个图连通(树形结构),该点即为汇点。 ### 算法步骤 1. 统计每个节点的出度(即从该节点出发的边数)。 2. 遍历所有节点,找出所有出度为 $0$ 的点: - 若有且仅有一个,输出该点。 - 若有多个或不存在,输出 $-1$(实际不可能不存在,因总出度=$N-1$)。 ### 代码实现 ```python n = int(input().strip()) out_degree = [0] * (n + 1) # 初始化出度数组(索引1~n) # 读取N-1条边,更新出度 for _ in range(n - 1): a, b = map(int, input().split()) out_degree[a] += 1 # a有一条出边指向b # 找出所有出度为0的点 sinks = [] for i in range(1, n + 1): if out_degree[i] == 0: sinks.append(i) # 判断结果 if len(sinks) == 1: print(sinks[0]) else: print(-1) ``` ### 示例说明 - **输入**: `3` `1 2` `3 2` - **处理**: 节点1出度=1,节点2出度=0,节点3出度=1。 唯一出度为0的点是节点2,输出 `2`。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值