[十二省联考2019]春节十二响
题目大意:给出一棵树,要求把这棵树分为若干个集合,集合中不能包含祖先-后代关系,使每个集合中的最大值之和最小。
- 25分
subtask里有一种链的情况,且1号不一定是链的一个端点。也就是说,树是一个根节点连着两条链。
假设两条链的点数分别为$m,n(m>n)$。这种情况里,前$n$个点可以两两分为一个集合,后$(m-n)$个点和根节点各自独立为一个集合。
所以,可以把两条链看作两个优先队列,每次比较它们的$top$,把小的向大的合并为一条链,最后再加上根节点。
答案即为$sigma(i=2$~$n)max(w[mi],w[ni] + sigma(i=m-n+1$~$m)w[mi] + w[1]$
考场上半个小时码完了qwq
然而不知道当时怎么推的,举例的时候以为这个思路是错的就没往下想
- 60分
其实这种做法可以由特殊推广到一般。
对于一个有若干个子链的点,可以把这些链两两合并,成为一条链。树上的每个子树,都可以用这种方法合并为一条链。
所以每个节点都开一个优先队列,对于一个根节点,首先遍历它的每一棵子树(链),将这条链($q[v]$)与当前根节点所连的最大链(记为$q[u]$)比较,若$q[v].size>q[u].size$则交换。
将较小的链($q[v]$)合并到较大的链($q[u]$)。遍历所有子树后,将根节点加入$q[u]$。
void dfs(int u) { for(int i = head[u]; i; i = nxt[i]) { int v = to[i]; dfs(v); if(q[v].size() > q[u].size()) swap(q[u],q[v]); int cnt = 0; while(!q[v].empty()) { tem[++cnt] = max(q[u].top(),q[v].top()); q[u].pop(); q[v].pop(); } for(int i = 1; i <= cnt; i++) q[u].push(tem[i]); } q[u].push(w[u]); }
- 100分
这种做法是很麻烦的。想象一条链的情况,需要每次比较儿子和父亲,交换儿子和父亲,把父亲加入队列…
因为最下面的儿子是最先遍历到的,所以可以看作把父亲不停地加入儿子的链。
每次直接把第一个儿子作为最大链;记录每个点的编号,把儿子的编号传递给父亲
void dfs(int u) { id[u] = u; for(int i = head[u]; i; i = nxt[i]) { int v = to[i]; dfs(v); if(i == head[u]){ id[u] = id[v]; continue; } …… } q[id[u]].push(w[u]); }
这样就快很多啦w
代码如下
#include<cstdio> #include<iostream> #include<queue> #define MogeKo qwq #define ll long long using namespace std; const int maxn = 2e5+10; int n,f,cnt; ll w[maxn],tem[maxn],id[maxn]; ll ans; int head[maxn],to[maxn],nxt[maxn]; priority_queue <ll> q[maxn]; void add(int x,int y) { to[++cnt] = y; nxt[cnt] = head[x]; head[x] = cnt; } void dfs(int u) { id[u] = u; for(int i = head[u]; i; i = nxt[i]) { int v = to[i]; dfs(v); if(i == head[u]) { id[u] = id[v]; continue; } if(q[id[v]].size() > q[id[u]].size()) swap(id[u],id[v]); int cnt = 0; while(!q[id[v]].empty()) { tem[++cnt] = max(q[id[u]].top(),q[id[v]].top()); q[id[u]].pop(); q[id[v]].pop(); } for(int i = 1; i <= cnt; i++) q[id[u]].push(tem[i]); } q[id[u]].push(w[u]); } int main() { scanf("%d",&n); for(int i = 1; i <= n; i++) scanf("%lld",&w[i]); for(int i = 1; i <= n-1; i++) { scanf("%d",&f); add(f,i+1); } dfs(1); while(!q[id[1]].empty()) { ans += q[id[1]].top(); q[id[1]].pop(); } printf("%lld",ans); return 0; }
不知道为啥Luogu把这题评成黑的...明明就queue+一个dfs,才60行代码...