题目来源及知识点
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 打包烧饼
本题使用的就是贪心,先用减去,得到每个餐盒里还需要的烧饼的数量,然后将其从小到大排序,优先往需求小的餐盒里放,这样能保证最后能被放满的餐盒数量最多。
#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的问题。
令表示在以i为子树的员工中选择一些人参加活动且不包括i时的总happy值,表示以i为子树的员工中选择一些人参加活动且包括i时的happy值。
当第i个人不参加活动时,它的直接下属(子节点)可以参加,也可以不参加,即状态转移方程为
(表示i的子节点的集合,下同)
当第i个人参加活动时,它的直接下属(字节点)一定不参加活动,即状态转移方程为
( 表示第i个人的happy值)
则我们需要找到的是以老板作为根节点时的最大值,假设老板节点为boss,即要求解,那么本题就可以以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 十七的秘密
本题也是采用动态规划,表示第i天新增知道秘密的人数,初始时,其余为0。对于,新增的人数为区间内知道秘密的人数之和,那么最终答案就是区间内知道秘密的人数之和。
根据以上分析实际上我们可以采用前缀和来求区间和,因此表示第1天到第i天新增的知道秘密的人的前缀和,则状态转移方程为
那么最终第n天知道还秘密的人数就是,要注意的是和不能为负。
#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;
}