- 给定一棵(n)个点的树,每个点有一个重量(w_i)和一个价值(v_i),每条边有一个长度。
- 对所有(w_i)之和不超过(m)的树上连通块,定义其中(v_i)之和最大的那些连通块对应的点集为完美的集合。
- 现要从这些完美的集合中选出(k)个,找到一个关键点(x)位于(k)个点集的交集中,且(x)与这(k)个点集的并集中任意一点(y)满足(dis(x,y) imes v_yle)给定的参数(lim)。
- 求有多少种选择集合的方式,使得存在至少一个符合条件的关键点(x)。
- (nle60,mle10^4,kle10^9),模数为(5^{23})
树上连通块=点-边
发现判定条件(dis(x,y) imes v_y)乘上的系数是(v_y)而非(v_x),故实际上我们可以保证对于选中的(k)个点集,所有符合条件的关键点(x)构成一个连通块。(证明:任意两个关键点间路径上的点肯定比它们更符合条件)
对于这种树上连通块问题,由于连通块内各点是相互独立的,有一个常见的容斥计算方式,就是用点的贡献减去边的贡献。
具体地,我们先求出每个点作为关键点的方案数,再减去每条边上两点能够同时作为关键点的方案数,也就是用关键点数减去关键边数,便得到了关键连通块数。
树上连通块经典(DP)
要求点(x)作为关键点的方案数时,我们仅保留能被它测试的点。
然后就是一个经典的树上连通块(DP),设(f_{i,j})表示(DP)到(dfs)序列上第(i)位,已占重量为(j)时的最大价值和(可以绑成(struct)同时维护方案数),转移考虑两种情况:
- 当前位置选择,直接更新重量和及价值和转移到第(i+1)位。
- 当前位置不选,则它子树内都不能选,由于子树在(dfs)序列上对应一段区间,直接转移到这段区间右端点(+1)的位置即可。
如果是要求((x,y))作为关键边的方案数,就仅保留能同时被它俩测试的点。因为它俩都是必选的,可以任选其中一个为根,并在枚举到它们时强制执行第一种转移即可。
我们一开始先不管测试,保留整棵树,枚举每个点作根,利用上面的(DP)求出完美集合的价值之和(Mx)。之后只要判断价值之和是否为(Mx)即可判断是否为完美的集合了。
组合数取模
取模问题是这道题的又一大难点。。。
要求(C_x^k),只需分别求出(x!,k!,(x-k)!),并把它们表示成(v imes 5^p)的形式。
首先有(n!=prod_{i=1}^n[iperp 5]i imes (lfloorfrac n5 floor! imes 5^{lfloorfrac n5 floor})),式子中(lfloorfrac n5 floor!)可以递归求解,因此主要问题就变成了求(prod_{i=1}^n[iperp 5]i)。
令(F_n(x)=prod_{i=1}^n[iperp5](x+i)),我们要求的就是(F_n(0))。
考虑倍增,(F_{10a}(x)=F_{5a}(x)*F_{5a}(x+5a))。其中(F_{5a}(x+5a))可以用二项式定理展开,因为模数是(5^{23}),所以只需保留第(0sim 22)次项即可。
这样就能求出(F_{10 imes lfloorfrac n{10} floor}(x)),而它与(F_n(x))只相差不到(10)个二项式,直接暴力卷上就好了。
代码:(O()跑不满())
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 60
#define M 10000
#define X 11920928955078125
#define LL long long
#define add(x,y,z) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].l=z)
using namespace std;
int n,m,k,w[N+5],v[N+5];LL lim;int ee,lnk[N+5];struct edge {int to,nxt,l;}e[2*N+5];
I LL QM(LL x,LL y,LL p) {LL k=x*y-(LL)(1.0L*x*y/p)*p;return (k%p+p)%p;}
I LL QP(LL x,LL y,LL p) {LL t=1;W(y) y&1&&(t=QM(t,x,p)),x=QM(x,x,p),y>>=1;return t;}
namespace FastIO
{
#define FS 100000
#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
char oc,FI[FS],*FA=FI,*FB=FI;
Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
}using namespace FastIO;
namespace S//组合数取模
{
LL p,X_,C[23][23];I void InitC()//预处理组合数
{
RI i,j;for(C[0][0]=i=1;i^p;++i) for(C[i][0]=j=1;j^p;++j) C[i][j]=(C[i-1][j-1]+C[i-1][j])%X_;
}
struct Poly
{
LL v[23];I Poly() {memset(v,0,sizeof(v));}I Poly(Con LL& x) {memset(v,0,sizeof(v)),v[0]=x%X_,v[1]=1;}
I LL& operator [] (CI x) {return v[x];}I LL operator [] (CI x) Con {return v[x];}
I Poly operator * (Con Poly& o) Con//暴力卷积
{
Poly t;for(RI i=0;i^p;++i) for(RI j=0;j^(p-i);++j) (t[i+j]+=QM(v[i],o[j],X_))%=X_;return t;
}
};
I Poly Upt(Con Poly& f,Con LL& x)//二项式定理展开
{
Poly t;RI i,j;LL w;for(i=0;i^p;++i) for(w=1,j=0;
j<=i;w=QM(w,x,X_),++j) (t[i-j]+=QM(f[i],QM(C[i][j],w,X_),X_))%=X_;return t;
}
I Poly Solve(Con LL& x)//倍增
{
if(!x) {Poly F;return F[0]=1,F;}LL w=x/10*10;Poly F=Solve(w/2);F=F*Upt(F,w/2);//倍增
for(LL i=w+1;i<=x;++i) i%5&&(F=F*Poly(i),0);return F;//暴力卷上多余项
}
I LL Fac(LL x) {LL t=1;W(x) t=QM(t,Solve(x)[0],X_),x/=5;return t;}//除去5之后的阶乘
I LL Cnt(LL x) {LL t=0;W(x) t+=(x/=5);return t;}//阶乘中5的幂次
I LL Calc(Con LL& x)//计算C(x,k)
{
if(x<k) return 0;p=23-(Cnt(x)-Cnt(k)-Cnt(x-k));if(p<=0) return 0;X_=QP(5,p,X+1),InitC();//给模数除去5的幂次
LL A=Fac(x),B=QM(Fac(k),Fac(x-k),X_);return QM(A,QP(B,X_/5*4-1,X_),X_)*(X/X_);//计算Fac(x)/Fac(k)/Fac(x-k)
}
}
namespace T//树上连通块问题
{
int q[N+5],dis[N+5][N+5];I void bfs(CI x)//预处理距离
{
RI i,k,H=1,T=1;q[1]=x;W(H<=T) for(i=lnk[k=q[H++]];i;i=e[i].nxt)
e[i].to^x&&!dis[x][e[i].to]&&(dis[x][q[++T]=e[i].to]=dis[x][k]+e[i].l);
}
int p[N+5],d,dI[N+5],dO[N+5],s[N+5];I void dfs(CI x,CI lst=0)//遍历求dfs序
{
s[dI[x]=++d]=x;for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&p[e[i].to]&&(dfs(e[i].to,x),0);dO[x]=d;
}
struct node
{
LL v,c;I node(Con LL& a=-1,Con LL& b=0):v(a),c(b){}
I node operator + (CI o) {return node(v+o,c);}
I void operator += (Con node& o) {v^o.v?v<o.v&&(v=o.v,c=o.c):c+=o.c;}
}f[N+5][M+5];
I node DP(CI x,CI y)//树上连通块经典DP
{
RI i,j;for(d=0,dfs(x),i=1;i<=d+1;++i) for(j=0;j<=m;++j) f[i][j]=node();//清空
for(f[1][0]=node(0,1),i=1;i<=d;++i) for(j=0;j<=m;++j) f[i][j].c&&
(j+w[s[i]]<=m&&(f[i+1][j+w[s[i]]]+=f[i][j]+v[s[i]],0),s[i]^x&&s[i]^y&&(f[dO[s[i]]+1][j]+=f[i][j],0));//选;不选
node res;for(j=0;j<=m;++j) res+=f[d+1][j];return res;//统计总答案
}
LL Mx;I LL Solve(CI x,CI y)//计算关键点(y=0)或关键边的贡献
{
RI i,j;for(i=1;i<=n;++i) p[i]=1LL*v[i]*max(dis[x][i],dis[y][i])<=lim;//仅保留能测试的点
if(!p[x]||(y&&!p[y])) return 0;node t=DP(x,y);return t.v^Mx?0:S::Calc(t.c);//返回C(t.c,k)
}
I LL GetAns()
{
RI i;LL t=0;for(i=1;i<=n;++i) p[i]=1;for(i=1;i<=n;++i) Mx=max(Mx,DP(i,0).v);//不管测试,求出完美集合的价值之和
for(i=1;i<=n;++i) (t+=Solve(i,0))%=X;for(i=1;i<=ee;i+=2) (t+=X-Solve(e[i].to,e[i+1].to))%=X;return t;//点-边
}
}
int main()
{
RI i;for(read(n,m,k,lim),i=1;i<=n;++i) read(w[i]);for(i=1;i<=n;++i) read(v[i]);
RI x,y,z;for(i=1;i^n;++i) read(x,y,z),add(x,y,z),add(y,x,z);
for(i=1;i<=n;++i) T::bfs(i);return printf("%lld
",T::GetAns()),0;
}