【ACM算法竞赛日常训练】DAY3题解与分析【旅游】【tokitsukaze and Soldier】
DAY3共2题:
-
旅游
-
tokitsukaze and Soldier
🎈 作者:Eriktse
🎈 简介:19岁,211计算机在读,现役ACM银牌选手🏆力争以通俗易懂的方式讲解算法!❤️欢迎关注我,一起交流C++/Python算法。(优质好文持续更新中……)🚀
🎈 原文链接(阅读原文获得更好阅读体验):
旅游
题目传送门:https://ac.nowcoder.com/acm/problem/15748
该题主要考察对树的理解,以及简单的树上dp和贪心算法。
我们将会住的节点标记为1
,其余不住的节点标记为0
。
我们可以发现,根节点(s
)是一定会标记为1的,那么剩下的节点该怎么分配可以使得标记为1的节点数最多呢?
当我们在某个点x
标记时,我们可以发现它的父亲、儿子们都不能再被标记了。但是点x
的兄弟却不受影响,接下来考虑一下哪些节点的兄弟多呢?应该是叶子节点。
所以我们可以想到首先将根节点和叶子结点全部都标记为1,然后遍历整棵树,如果某个点的父亲和儿子们都没被标记,那么他也可以被标记为1
。
注意考虑特殊情况,比如只有一个点的树,只有两个点的树....
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 5e5 + 9, inf = 8e18;
bitset<maxn> sel;
vector<int> g[maxn];
int n, s;
void dfs(int x, int pre)
{
//如果到了叶子节点,就直接标记并返回
//这里sel[x] = !sel[pre]是考虑到叶子的深度可能为2(即叶子的父亲就是根)
//此时根一定被标记,那么叶子就不能被标记
//如果是一般情况,那么父亲肯定不会被标记(因为父亲的标记需要儿子处理完成之后再决定)
//自己就打上标记
if(g[x].size() == 1 and x != s)return sel[x] = !sel[pre], void();
bool tag = true;//tag == true表示当前点可以被标记
if(x == s)sel[x] = true;//根一定被标记
else if(sel[pre])tag = false;//如果父亲被标记了,那么当前点一定不能被标记
//看看儿子们是否被标记
for(auto &y : g[x])
{
if(y == pre)continue;
dfs(y, x);
if(sel[y])tag = false;
}
sel[x] = tag;
}
signed main()
{
scanf("%lld %lld", &n, &s);
for(int i = 1;i < n; ++ i)
{
int x, y;scanf("%lld %lld", &x, &y);
g[x].push_back(y), g[y].push_back(x);
}
dfs(s, 0);
int ans = 0;
for(int i = 1;i <= n; ++ i)
if(sel[i])ans ++;//统计标记的点的个数
printf("%lld\n", ans);
return 0;
}
做完这道题,我们可以总结一点点对于树上dp这一类题的经验技巧:
1.将特殊点作为根,建立一棵树,建树一般用双向边,边的条数严格等于点的个数-1。
2.优先考虑树中特殊的点,比如根、叶子。
3.不要忘记考虑特殊情况,比如一条链状的树(此时注意根是否会被判定为叶子、注意复杂度是否会爆)、仅有1个点的树(可能需要特判)。
tokitsukaze and Soldier
题目传送门:https://ac.nowcoder.com/acm/problem/50439
这题主要考察贪心+优先队列维护区间k个最值。
我们观察题目可以发现,当我们枚举到一个士兵的要求是"队伍人数不能超过s[i] = k
"时,那么此时军队的战斗力最大值应该是所有s >= k
的军人中的最大的k
个军人的战斗力之和。
我们可以考虑用一个优先队列维护最大的k
个值之和(小根堆,每次弹出最小值,即可维护最大值之和),然后通过限制“遍历方式”使得当遍历到第i
个人(s[i] = k
)时,优先队列里的所有值对应的s
都是>= k
的,这样就只需要求出优先队列里最大的k个数字之和即可,这个遍历方式就是按照s[i]
降序排列,然后从前往后遍历。
我们可以维护一个大小始终等于s[i]
的优先队列,因为s[i]
为降序,所以这个优先队列的元素个数的最大值是一直在减小的,减小的时候只需要弹出最小的元素即可,用一个变量sum
维护优先队列里的所有元素之和(push
时加上,pop
时减去)。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 1e5 + 9;
struct Node
{
int v, s;
}a[maxn];
signed main()
{
int n;scanf("%lld", &n);
for(int i = 1;i <= n; ++ i)scanf("%lld %lld", &a[i].v, &a[i].s);
sort(a + 1,a + 1 + n, [](const Node &u, const Node &v)
{
return u.s > v.s;
});
priority_queue<int, vector<int>, greater<int> > pq;
int ans = 0, sum = 0;
for(int i = 1;i <= n; ++ i)
{
sum += a[i].v, pq.push(a[i].v);
while(pq.size() > a[i].s)sum -= pq.top(), pq.pop();
ans = max(ans, sum);
}
printf("%lld\n", ans);
return 0;
}
经验总结:
1.用某种遍历方式来限制某个条件,比如本题用"降序"来限制在当前点之前的所有点的s[i]
都比当前的大或相等。
2.优先队列可以维护区间的k
个最值之和,只适用于连续的查询且k
只能变小或不变,因为变大的话,不知道要将哪一个放进去。
最后
感谢大家的阅读,欢迎大家跟我一起刷题呀!
🎈 本文由eriktse原创,创作不易,如果对您有帮助,欢迎小伙伴们点赞👍、收藏⭐、留言💬