CF486D-Valid Sets

题目

给出一个\(n\)个点的树,每个点有权值\(a_i\),再给出一个\(d\),问有多少个非空点集满足:

  • 点集在树上构成联通子图
  • \[\max _{v\in S}a_v -\min _{v\in S}\le d$$,即集合内权值最大减最小在$d$以内 \]

分析

首先如何求树上联通子图的总数?设\(f_i\)表示包含\(i\)点的联通子图个数,那么有:

\[f_x=\prod _{(x,v)\in E}(f_v+1) \]

即考虑是否包含每个子树中的集合,如果不包含就给其他的乘1,否则就乘上子树中的集合个数。

如果有\(d\)的条件怎么办呢?

为了不算重,我们对每个点\(x\)做树形dp,强制\(a_x\)为权值最小的,那么只进入\(a_x\le a_v\le a_x+d\)的子树进行计算。但这样依然会算重,原因是如果有相同权值的点,那么可能会在这些点都统计到同一个集合。

于是我们给它定序。重复计算其实就是没有一个顺序来区分这些集合。所以当遇到\(a_x=a_v\)的时候,只有\(v>x\)我们才走进去,这样就相当于给集合中相同最小权值的点定序,去除了重复的情况。

代码

#include<cstdio>
#include<cctype>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long giant;
int read() {
	int x=0,f=1;
	char c=getchar();
	for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;
	for (;isdigit(c);c=getchar()) x=x*10+c-'0';
	return x*f;
}
const int maxn=2e3+1;
const int q=1e9+7;
int n,d,a[maxn],f[maxn],ans=0;
vector<int> g[maxn];
inline void add(int x,int y) {g[x].push_back(y);}
inline int Plus(int x,int y) {return ((giant)x+(giant)y)%q;}
inline int Multi(int x,int y) {return (giant)x*y%q;}
void dfs(int x,int fa,int id,int lim) {
	int &fx=f[x]=1;
	for (int v:g[x]) if (v!=fa && ((lim<a[v] && a[v]<=lim+d) || (lim==a[v] && v>id))) dfs(v,x,id,lim),fx=Multi(f[x],f[v]+1); 
}
int main() {
#ifndef ONLINE_JUDGE
	freopen("test.in","r",stdin);
#endif
	d=read(),n=read();
	for (int i=1;i<=n;++i) a[i]=read();
	for (int i=1;i<n;++i) {
		int x=read(),y=read();
		add(x,y),add(y,x);
	}
	for (int i=1;i<=n;++i) {
		memset(f,0,sizeof f);
		dfs(i,i,i,a[i]);
		ans=Plus(ans,f[i]);
	}
	printf("%d\n",ans);
	return 0;
}
posted @ 2017-07-10 10:52  permui  阅读(307)  评论(0编辑  收藏  举报