CF486D-Valid Sets

题目

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

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

\(n,a_i,d\le 2\times 10^3\)

分析

首先如何求树上联通子图的总数?设\(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;
}

转载于:https://www.cnblogs.com/owenyu/p/7144699.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值