重庆交通大学程序设计竞赛集训队暑假第三次练习赛——题解

题目来源及知识点

A:(贪心)

B:《算法竞赛进阶指南》-0x54.例题.没有上司的舞会(树形DP)

C:USACO 2015 January Contest Bronze-A.Cow Routing(模拟)

D:LeetCode.2327-知道秘密人数前缀和、DP)

E:CodeForce Round #806(Div.4)-B.ICPC Balloons(模拟)

(由于C、E两题较简单所以就没有写解析,代码必要地方有注释)

Problem.A 打包烧饼

本题使用的就是贪心,先用c[i]减去r[i],得到每个餐盒里还需要的烧饼的数量,然后将其从小到大排序,优先往需求小的餐盒里放,这样能保证最后能被放满的餐盒数量最多

#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long

const int N = 50010;
int n, m, r, c[N];

void solve()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        cin >> c[i];
    }
    for (int i = 1; i <= n; i++)
    {
        cin >> r;
        c[i] -= r;
    }
    sort(c + 1, c + 1 + n);
    int ans = 0;
    for (int i = 1; i <= n; i++)
    {
        if (m < c[i])
            break;
        ans++;
        m -= c[i];
    }
    cout << ans << endl;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    solve();
    return 0;
}

Problem.B 开黑之夜

本题很容易知道这个关系结构是个树形结构,每个人的happy值都作为节点的权值。要求解的是最优子结构,那么这就是个树形DP的问题。

dp[i][0]表示在以i为子树的员工中选择一些人参加活动且不包括i时的总happy值,dp[i][1]表示以i为子树的员工中选择一些人参加活动且包括i时的happy值。 

当第i个人不参加活动时,它的直接下属(子节点)可以参加,也可以不参加,即状态转移方程为

dp[i][0]=\sum_{s\in Son(i)}^{}max(dp[s][0],dp[s][1])

Son(i)表示i的子节点的集合,下同)

当第i个人参加活动时,它的直接下属(字节点)一定不参加活动,即状态转移方程为

dp[i][1]=a[i]+\sum_{s\in Son(i)}^{}dp[s][0]

 表示第i个人的happy值

则我们需要找到的是以老板作为根节点时的最大值,假设老板节点为boss,即要求解max(dp[boss][0],dp[boss][1]),那么本题就可以以DFS的方式来求出每个子树的最大happy值依次向上传递。

#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long

const int N = 6010, M = 12010;
int n, a[N]; //a[]:happy值
int h[N], e[M], ne[M], idx; //邻接表存储树
int dp[N][2];
bool st[N]; //该节点是否有父节点

void init()
{
    memset(h, -1, sizeof(h));
    idx = 0;
}

//连接一条从p到s的边,p是s的直接上级
void add(int p, int s)
{
    e[idx] = s;
    ne[idx] = h[p];
    h[p] = idx++;
}

void dfs(int u)
{
    dp[u][1] = a[u];
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        dfs(j);
        dp[u][0] += max(dp[j][0], dp[j][1]);
        dp[u][1] += dp[j][0];
    }
}

void solve()
{
    init();
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    int s, p;
    for (int i = 1; i < n; i++)
    {
        cin >> s >> p;
        add(p, s);
        st[s] = true;
    }
    int boss= 1;
    //没有父节点的点就是老板
    while (st[boss])
    {
        boss++;
    }
    dfs(boss);
    cout << max(dp[boss][0], dp[boss][1]) << endl;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    solve();
    return 0;
}

Problem.C 旅游线路

#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long

int a, b, n;

void solve()
{
    cin >> a >> b >> n;
    int ans = 1010;
    while (n--)
    {
        int sum, m, pos;
        cin >> sum >> m;
        bool flag = false;
        while (m--)
        {
            cin >> pos;
            if (pos == a) //找到了出发起点
                flag = true;
            if (pos == b && flag) //找到了终点
                ans = min(ans , sum);
        }
    }
    if (ans == 1010)
        cout << -1 << endl;
    else
        cout << ans <<endl;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    solve();
    return 0;
}

Problem.D 十七的秘密

本题也是采用动态规划,f[i]表示第i天新增知道秘密的人数,初始时f[1]=1,其余为0。对于f[i],新增的人数为区间[i-d2+d1,i-d1]内知道秘密的人数之和,那么最终答案就是区间[n-d2+d1,n]内知道秘密的人数之和。

根据以上分析实际上我们可以采用前缀和来求区间和,因此dp[i]表示第1天到第i天新增的知道秘密的人的前缀和,则状态转移方程为

dp[i]=dp[i-1]+dp[i-d1]-dp[i-d2]

那么最终第n天知道还秘密的人数就是dp[n]-dp[n-d2],要注意的是i-d1i-d2不能为负。

#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long

const int N = 1010, mod = 1e9 + 7;
int n, d1, d2, dp[N];

void solve()
{
    cin >> n >> d1 >> d2;
    dp[0] = 0, dp[1] = 1;
    for (int i = 2; i <= n; i++)
    {
        dp[i] = dp[i - 1];
        if (i >= d1)
            dp[i] = (dp[i] + dp[i - d1]) % mod;
        if (i >= d2)
            dp[i] = (dp[i] - dp[i - d2]) % mod;
    }
    cout << ((dp[n] - dp[n - d2]) % mod + mod) % mod << endl;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    solve();
    return 0;
}

Problem.E ICPC的气球

#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long

map<char,int> ball;
int t, n;
string s;

void solve()
{
    cin >> s;
    int ans = 0;
    for (auto c: s)
    {
        if (ball[c] == 0) //如果该字母第一次出现
            ans+=2; //加两个气球
        else
            ans++;

        ball[c]++; //存入map
    }
    cout << ans << endl;
    ball.clear(); //处理完一组数据清空map
}

signed main()
{
ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cin >> t;
    while (t--)
    {
        solve();
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值