2023年10月12日-秋招-留学生-第三题(300分)-塔子哥的分糖果方案

在线评测链接

题目描述

塔子哥组织小朋友们在玩一场小游戏, 一共有 n n n个小朋友, 编号为 0... n − 1 0 ... n-1 0...n1, 每个小朋友身后都有一根绳子, 一个小朋友可以不拿或者拿多个同学的绳子, 拿完之后塔子哥会让小朋友们保持不动, 给小朋友发糖果。

给一个小朋友分糖果, 他就会开心, 否则他就会不开心。

可是塔子哥的糖果没有很多了, 怎么分才能使最少的糖果让每个绳子两头都最少有一个开心的小朋友呢

(一个小朋友只能被一个小朋友牵, 一个小朋友可以牵多个小朋友, 最终类似一个树形结构, 题目保证所有小孩子都牵了人或者被人牵)

输入描述

第一行一个整数 n n n,表示小朋友数量 ( 1 ≤ n ≤ 1500 ) (1\le n\le 1500) (1n1500)
接下来 n n n行,每行以 a : ( b ) a:(b) a:(b) 这样的格式开头, a a a表示小朋友的编号 ( 0 ≤ a ≤ n − 1 ) (0\le a\le n-1) (0an1)
b b b表示第 i i i个小朋友牵了几个小朋友,接下来 b b b个数,表示被牵的小朋友的编号

输出描述

一个数字,为需要的糖果的最少个数

样例

输入1

3
0:(2) 1 2
1:(0)
2:(0)

输出1

1

说明

0号小朋友与1号和2号相连,那么只需要给0号发一个糖果就可使得所有绳子两端都有开心的小朋友

输入2

8
0:(3) 1 2 3
1:(1) 6
2:(0)
3:(0)
6:(1) 7
7:(2) 4 5
4:(0)
5:(0)

输出2

3

说明
0, 6, 7号小朋友发糖果, 就可使得所有绳子两端都有开心的小朋友

题解:树形DP

定义 f [ i ] [ j ] f[i][j] f[i][j]为以 i i i为结点的子树中,不放消防栓/放消防栓 j = 0 / 1 j=0/1 j=0/1)的最小个数

如果在第 i i i个结点放置消防栓,那么对于结点 i i i的所有子节点,都可以放置/不放置消防栓

则有 f [ i ] [ 1 ] = ∑ m i n ( f [ u ] [ 0 ] , f [ u ] [ 1 ] ) ( u ∈ i ∣ u 是 i 的子节点 ) f[i][1]=\sum_{}^{}min(f[u][0],f[u][1]) (u\in {i|u是i的子节点}) f[i][1]=min(f[u][0],f[u][1])(uiui的子节点)

如果在第 i i i个结点不放置消防栓,那么对于结点 i i i的所有子节点,都必须要放置消防栓

则有 f [ i ] [ 0 ] = ∑ f [ u ] [ 1 ] ( u ∈ i ∣ u 是 i 的子节点 ) f[i][0]=\sum_{}^{}f[u][1] (u\in {i|u是i的子节点}) f[i][0]=f[u][1](uiui的子节点)

C++

#include<bits/stdc++.h>
using namespace std;
int n;
vector<vector<int>>g;
const int N=1510;
int f[N][2],d[N];
void dfs(int u)
{
    f[u][1]=1;f[u][0]=0;
    for(int &x:g[u])
    {
        dfs(x);
        f[u][1]+=min(f[x][0],f[x][1]);
        f[u][0]+=f[x][1];
    }
    
}
int main()
{
    cin>>n;
    g.resize(n);
    memset(d,0,sizeof d);
    int t,cnt;
    for(int i=0;i<n;i++)
    {
        scanf("%d:(%d)",&t,&cnt);
        while(cnt--)
        {
            int a;
            cin>>a;
            g[t].push_back(a);
            d[a]++;
        }
    }
    int root=0;
    while(d[root])root++;
    dfs(root);
    int res=min(f[root][0],f[root][1]);
    cout<<res<<endl;
    return 0;
}

Java

import java.util.*;

public class Main {
    static int n;
    static List<List<Integer>> g;
    static final int N = 1510;
    static int[][] f = new int[N][2];
    static int[] d = new int[N];

    static void dfs(int u) {
        f[u][1] = 1;
        f[u][0] = 0;
        for (int x : g.get(u)) {
            dfs(x);
            f[u][1] += Math.min(f[x][0], f[x][1]);
            f[u][0] += f[x][1];
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        g = new ArrayList<>(n);
        for (int i = 0; i < n; i++) {
            g.add(new ArrayList<>());
        }
        Arrays.fill(d, 0);
        for (int i = 0; i < n; i++) {
            String[] data = sc.next().split(":");
            int t = Integer.parseInt(data[0]);
            int cnt = Integer.parseInt(data[1].substring(1, data[1].length() - 1));
            while (cnt-- > 0) {
                int a = sc.nextInt();
                g.get(t).add(a);
                d[a]++;
            }
        }
        int root = 0;
        while (d[root] > 0) {
            root++;
        }
        dfs(root);
        int res = Math.min(f[root][0], f[root][1]);
        System.out.println(res);
    }
}

Python

import re

n = int(input())
g = [[] for _ in range(n)]
N = 1510
f = [[0, 0] for _ in range(N)]
d = [0 for _ in range(N)]

def dfs(u):
    f[u][1] = 1
    f[u][0] = 0
    for x in g[u]:
        dfs(x)
        f[u][1] += min(f[x][0], f[x][1])
        f[u][0] += f[x][1]

for _ in range(n):
    data = re.findall(r'\d+', input())
    t = int(data[0])
    cnt = int(data[1])
    for i in range(2, 2 + cnt):
        a = int(data[i])
        g[t].append(a)
        d[a] += 1

root = 0
while d[root] > 0:
    root += 1

dfs(root)
res = min(f[root][0], f[root][1])
print(res)

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

塔子哥学算法

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值