[HDU多校]Ridiculous Netizens

  • 点分治

    • 分成两个部分:对某一点P,连通块经过P或不经过P.
    • 经过P采用树形依赖背包
    • 不经过P的部分递归计算
  • 树型依赖背包

    • v点必须由其父亲u点转移过来
    • 即必须经过P点
    • \(dp[v][s*a[v]]+=dp[u][s]\)
    • 第二维代表连通块的乘积
    • 第一维代表经过该点并且一定经过P点的方案数
    • 所以最后父节点还要加上子节点的方案数
  • 空间优化

    • 第二维不能开这么大
    • 稍稍转变含义,改成还能"装下"多少体积
    • \(\lfloor \frac{m}{s}\rfloor\)就可以划分很多个等价的状态
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 2010;
const int mod = 1e9+7;
struct edge{
    int to,next;
}e[maxn<<1];
ll val[maxn],kuai[maxn];
ll dp[maxn][maxn];
int nowN,rt,maxP,head[maxn],tol,cnt,n;
int Size[maxn];
bool vis[maxn<<1];
ll ans;
int m;
inline void Mod(ll &a,ll b){
   a=a+b<mod?a+b:a+b-mod;
}
void init(int n){
    tol=1;
    for(int i=0;i<=n;++i){
        head[i]=0;
    }
    rt=0;
    cnt=ans=0;
}
void add(int u,int v){
    tol++;
    vis[tol]=0;
    e[tol].to=v;
    e[tol].next=head[u];
    head[u]=tol;
}
void get_rt(int u,int fa){
   int max_part=0;
   Size[u]=1;
   for(int i=head[u],v;i;i=e[i].next){
      v=e[i].to;
      if(vis[i]||v==fa)continue;
      get_rt(v,u);
      Size[u]+=Size[v];
      max_part=max(max_part,Size[v]);
   }
   max_part=max(nowN-Size[u],max_part);
   if(max_part<maxP){
     maxP=max_part;
     rt=u;
   }
}
void dfs(int u,int fa){
   for(int i=1;i<=cnt;++i) dp[u][i]=0;
   ll k=val[u],t;
   for(int i=1,j=1;i<=cnt;++i){
        t=kuai[i]/k;
        if(t==0)break;
        while(kuai[j]>t)++j;
        Mod(dp[u][j],dp[fa][i]);
   }
   for(int i=head[u],v;i;i=e[i].next){
      if(vis[i]) continue;
      v=e[i].to;
      if(v==fa) continue;
      dfs(v,u);
      for(int j=1;j<=cnt;++j) Mod(dp[u][j],dp[v][j]);
   }
}
void calc(){
   dp[0][1]=1;
   dfs(rt,0);
   for(int i=1;i<=cnt;++i) Mod(ans,dp[rt][i]);
   for(int i=head[rt],v;i;i=e[i].next){
       if(vis[i])continue;
       v=e[i].to;
       vis[i^1]=1;
       nowN=Size[v];
       maxP=n+5;
       rt=0;
       get_rt(v,0);
       calc();
   }
}
int main(){
    int _;
    scanf("%d",&_);
    while(_--){
        scanf("%d%d",&n,&m);
        init(n);
        for(int i=1;i<=n;++i)scanf("%lld",&val[i]);
        for(int i=1;i<=m;i=m/(m/i)+1){
            kuai[++cnt]=m/i;
        }
        for(int i=1,u,v;i<n;++i){
            scanf("%d%d",&u,&v);
            add(u,v);
            add(v,u);
        }
        nowN=n;
        maxP=n+5;
        get_rt(1,1);
        calc();
        printf("%lld\n",ans);
    }
    return 0;
}

代码量还是集中在点分治,不过套班子即可

DP还是好理解的.