CF486D Valid Sets

本文介绍了一种巧妙的动态规划方法,通过dp[u]计算在给定约束条件下,点u作为最小元素时的连通子图数目。关键在于利用最大-最小约束的特性避免重复,并在dfs中处理相同节点的贡献防止冗余。通过实例代码展示了如何使用这种方法解决实际问题。
摘要由CSDN通过智能技术生成

在这里插入图片描述
思路:比较妙的一个思路,考虑 d p ( u ) 为点 u 作为最小的 a u 情况下连通子图的数目 dp(u)为点u作为最小的a_u情况下连通子图的数目 dp(u)为点u作为最小的au情况下连通子图的数目
为什么要这么做,因为最大-最小<= d d d这个约束十分麻烦,如果我们在枚举点的同时,认为它是作为集合中的最大/最小,就能不重复不遗漏地统计出合法的子图.
接下来就是dp过程,+1代表可以不选择该子树一种方案. d p ( u ) = ∏ d p ( v ) + 1 dp(u)=\prod dp(v)+1 dp(u)=dp(v)+1
在dfs过程中,要特别注意 a [ u ] = = a [ v ] a[u]==a[v] a[u]==a[v],事实上对于这两种点,得到的连通子图事实上有重复的部分。当以 u u u为根的贡献算出来后,再次以 v v v为根时,如果统计了 u u u为根的子树,显然会造成重复部分的生成。因为这一部分已经在以 u u u为根的时候被统计出来过一次了,这要求在dfs(v)的时候我们不能再次访问 u u u节点.既然相同的点只能访问一次,不妨让数组编号小的访问大的.

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6+5;
const int INF = 1e9+7;
typedef long long ll;
typedef pair<int,int> pii;
#define all(a) (a).begin(), (a).end()
#define pb(a) push_back(a)
const int mod = INF;
vector<int> G[maxn];
int a[maxn];ll dp[maxn];int d,n;
void dfs(int u,int fa,int x){
	if(a[x]<a[u]||(a[x]==a[u]&&x<u)||(abs(a[u]-a[x])>d)) return ;
	dp[u] = 1;
	for(auto v : G[u]){
		if(v==fa) continue;
		dfs(v,u,x);
		dp[u] = 1LL*dp[u]*(dp[v]+1)%mod;
	}
}
int main(){
    ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>d>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n-1;i++){
		int u,v;cin>>u>>v;
		G[u].pb(v);G[v].pb(u);
	}
	ll ans = 0;
	for(int i=1;i<=n;i++){
		memset(dp,0,sizeof(dp));
		dfs(i,0,i);
		ans = (ans+dp[i])%mod;
	}
	cout<<ans<<"\n";
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

minato_yukina

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值