[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还是好理解的.