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;
}