题解 P4107 【[HEOI2015]兔子与樱花】

思路好想,卡常不好卡

题目链接

Solution [HEOI2015]兔子与樱花

题目大意:给定一个树,每个节点有一个权值。如果删除一个节点的话,就将它的权值加到它父节点上,并将它的儿子接到父节点上。要求在任意时刻每个节点的权值与儿子个数和小于常数\(m\),求最多可以删去多少个节点

贪心


分析:

比较显然的做法是,我们考虑一个节点的”危害":即删去它后,父节点的权值与儿子个数和会增加多少

危害值:\(w[x]=c[x]+son[x]-1\)

那么我们把危害值从小到大排序,贪心能删就删即可

删除一个节点同时会影响它父亲的危害值,所以你大可以手打斐波那契堆,事实上有一个更好的做法,我们优先删除深度大的节点。也就是按深度一层层贪心。有几点性质保证正确性

  • 1.如果先删除父节点再删除子节点合法,那么反过来一定合法

证明:设节点\(x\),父节点\(f\),祖父节点\(ff\)

先删除父亲满足

\(c[x]+c[f]+c[ff]+son[ff]-1+son[f]-1+son[x] \leq m\)

先删除子节点需要额外满足

\(c[x]+c[f]+son[f]-1+son[x] \leq m\)

\(\because son[ff] \geq 1\)

\(\therefore c[ff]+son[ff]-1 \geq 0\)

得证

  • 2.如果一个节点在第一次处理它时没有被删去,那么它以后也不可能再被删去了

如果将该节点的危害值加给它父亲不合法,那么如果要在以后删除它,哪怕它父亲、它祖父只有\(1\)个儿子,把它危害值加给它祖父也一定不合法了(\(c[x]+son[x]\)单调不降)

所以我们一层层贪心就可以了,删不了的就直接丢掉不管,为了优化可以写成\(dfs\)的形式。回溯的时候计算就可以了(兄弟节点互不影响)

#include <cstdio>
#include <cctype>
#include <vector>
#include <list>
#include <algorithm>
using namespace std;
const int maxn = 2e6 + 100;
namespace FastIO{
	const int bufsiz = 1 << 22;
	char buf[bufsiz];
	inline char getch(){
		static int tot;
		tot++;
		if(tot == bufsiz){
			fread(buf,1,bufsiz,stdin);
			tot = 0;
		}
		return buf[tot];
	}
	inline int read(){
		int x = 0;char c = getch();
		while(!isdigit(c))c = getch();
		while(isdigit(c))x = x * 10 + c - '0',c = getch();
		return x;
	}
}using FastIO::read;
vector<int> vec[maxn];
int c[maxn],son[maxn],head[maxn],nxt[maxn],to[maxn],node[maxn],l[maxn],r[maxn],n,m,ans,tot;
inline void addedge(int from,int to){
	static int tot;
	::to[++tot] = to;
	nxt[tot] = head[from];
	head[from] = tot;
}
inline void dfs(int u = 1,int faz = -1){
	for(int i = head[u];i;i = nxt[i]){
		int v = to[i];
		if(v == faz)continue;
		dfs(v,u);
	}
	if(!son[u])return;
	sort(node + l[u],node + r[u] + 1,[](int a,int b){return (son[a] + c[a]) < (son[b] + c[b]);});
	for(int i = l[u];i <= r[u];i++)
		if(c[u] + son[u] + c[node[i]] + son[node[i]] - 1 <= m){
			ans++;
			c[u] += c[node[i]];
			son[u] += son[node[i]] - 1;
		}
}
int main(){
	// freopen("fafa.in","r",stdin);
	n = read(),m = read();
	for(int i = 1;i <= n;i++)c[i] = read();
	for(int u = 1;u <= n;u++){
		int k = son[u] = read();
		if(!k)continue;
		l[u] = tot + 1;
		r[u] = tot + k;
		while(k--){
			int v = node[++tot] = read() + 1;
			addedge(u,v);
		}
	}
	dfs();
	printf("%d\n",ans);
	return 0;
}

猜你喜欢

转载自www.cnblogs.com/colazcy/p/13369561.html