其他分享
首页 > 其他分享> > 【LOJ2462】「2018 集训队互测 Day 1」完美的集合(树上连通块问题+组合数取模)

【LOJ2462】「2018 集训队互测 Day 1」完美的集合(树上连通块问题+组合数取模)

作者:互联网

点此看题面

树上连通块=点-边

发现判定条件\(dis(x,y)\times v_y\)乘上的系数是\(v_y\)而非\(v_x\),故实际上我们可以保证对于选中的\(k\)个点集,所有符合条件的关键点\(x\)构成一个连通块。(证明:任意两个关键点间路径上的点肯定比它们更符合条件)

对于这种树上连通块问题,由于连通块内各点是相互独立的,有一个常见的容斥计算方式,就是用点的贡献减去边的贡献。

具体地,我们先求出每个点作为关键点的方案数,再减去每条边上两点能够同时作为关键点的方案数,也就是用关键点数减去关键边数,便得到了关键连通块数。

树上连通块经典\(DP\)

要求点\(x\)作为关键点的方案数时,我们仅保留能被它测试的点。

然后就是一个经典的树上连通块\(DP\),设\(f_{i,j}\)表示\(DP\)到\(dfs\)序列上第\(i\)位,已占重量为\(j\)时的最大价值和(可以绑成\(struct\)同时维护方案数),转移考虑两种情况:

如果是要求\((x,y)\)作为关键边的方案数,就仅保留能同时被它俩测试的点。因为它俩都是必选的,可以任选其中一个为根,并在枚举到它们时强制执行第一种转移即可。

我们一开始先不管测试,保留整棵树,枚举每个点作根,利用上面的\(DP\)求出完美集合的价值之和\(Mx\)。之后只要判断价值之和是否为\(Mx\)即可判断是否为完美的集合了。

组合数取模

取模问题是这道题的又一大难点。。。

要求\(C_x^k\),只需分别求出\(x!,k!,(x-k)!\),并把它们表示成\(v\times 5^p\)的形式。

首先有\(n!=\prod_{i=1}^n[i\perp 5]i\times (\lfloor\frac n5\rfloor!\times 5^{\lfloor\frac n5\rfloor})\),式子中\(\lfloor\frac n5\rfloor!\)可以递归求解,因此主要问题就变成了求\(\prod_{i=1}^n[i\perp 5]i\)。

令\(F_n(x)=\prod_{i=1}^n[i\perp5](x+i)\),我们要求的就是\(F_n(0)\)。

考虑倍增,\(F_{10a}(x)=F_{5a}(x)*F_{5a}(x+5a)\)。其中\(F_{5a}(x+5a)\)可以用二项式定理展开,因为模数是\(5^{23}\),所以只需保留第\(0\sim 22\)次项即可。

这样就能求出\(F_{10\times \lfloor\frac n{10}\rfloor}(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\n",T::GetAns()),0;
}

标签:连通,LOJ2462,LL,times,数取模,2018,关键点,5a,define
来源: https://www.cnblogs.com/chenxiaoran666/p/LOJ2462.html