【LOJ2462】【2018 集训队互测 Day 1】完美的集合(容斥,树上连通块DP,exLucas)

点数-边数第二次见了(第一次是在十二省联考-希望),听说是树上连通块计数问题的一种经典方法?

考虑对于一个树上连通块来说,能对所有点进行测试的点(以下称为 “合法点”)肯定也构成了一个连通块。

由于选出的 K 个连通块有交,所以它们的并也是一个树上连通块,那么这个并中的合法点也构成了一个连通块。

考虑容斥,一般的容斥的形式是 ”钦定某几个点为合法点“,但注意到合法点一定构成连通块,所以可以用 ”钦定某个点为合法点“ 的选取方案数减去 ”钦定某条边的两个端点都为合法点“ 的选取方案数,这样每一种选取方案都恰好被统计了一次。

现在考虑求出某个点 x 为合法点的完美集合个数,然后再在里面选出 K 个的方案数即为钦定 x 为合法点的选取方案数。

先找到能被 x 测试的点集,那么现在就是问这个点集中有多少个包含 x 的子集为完美集合。

先把 x 提作为根,直接自底向上背包合并的复杂度为 O(nm2) 的。

延续之前的套路,我们不考虑合并,而是考虑逐个添加物品,在 dfs 序上转移,时间复杂度降至 O(nm)

注意过程中我们不能将 DP 值 X 取模,因为我们最后要求的是 (XK)。但你发现 DP 值其实是可以存的下的,因为 n60,那么 X260

现在的问题是多次求 (XK)mod523 的结果,其中 X260,K109K 固定。

使用 exLucas 即可。

#include<bits/stdc++.h>

#define N 65
#define M 10010
#define ll long long
#define LNF 0x7ffffffffffffff
#define fi first
#define se second
#define pll pair<ll,ll>
#define mk(a,b) make_pair(a,b)

using namespace std;

namespace modular
{
	const ll mod=11920928955078125;
	inline ll add(ll x,ll y){return x+y>=mod?x+y-mod:x+y;}
	inline ll dec(ll x,ll y){return x-y<0?x-y+mod:x-y;}
	inline ll mul(ll x,ll y){return (__int128)x*(__int128)y%mod;}
	inline void Add(ll &x,ll y){x=x+y>=mod?x+y-mod:x+y;}
	inline void Dec(ll &x,ll y){x=x-y<0?x-y+mod:x-y;}
	inline void Mul(ll &x,ll y){x=(__int128)x*(__int128)y%mod;}
}using namespace modular;

inline ll poww(ll a,ll b)
{
	ll ans=1;
	while(b)
	{
		if(b&1) ans=mul(ans,a);
		a=mul(a,a);
		b>>=1;
	}
	return ans;
}

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^'0');
		ch=getchar();
	}
	return x*f;
}

int n,m,k,val[N],wei[N];
ll lim,Sval;

namespace Tree
{
	int cnt=1,head[N],nxt[N<<1],to[N<<1],w[N<<1];
	int dis[N][N];
	void adde(int u,int v,int wi)
	{
		to[++cnt]=v;
		w[cnt]=wi;
		nxt[cnt]=head[u];
		head[u]=cnt;
	}
	void dfs(int u,int fa,int rt)
	{
		for(int i=head[u];i;i=nxt[i])
		{
			int v=to[i];
			if(v==fa) continue;
			dis[rt][v]=dis[rt][u]+w[i];
			dfs(v,u,rt);
		}
	}
	void init()
	{
		for(int i=1;i<=n;i++) dfs(i,i,i);
	}
	int rt1,rt2;
	ll f[M],g[M];
	void DP(int u,int fa)
	{
		static ll ff[N][M],gg[N][M];
		if(1ll*dis[rt1][u]*val[u]>lim||1ll*dis[rt2][u]*val[u]>lim) return;
		memcpy(ff[u],f,sizeof(ff[u]));
		memcpy(gg[u],g,sizeof(gg[u]));
		for(int j=m;j>=0;j--)
		{
			if(j<wei[u])
			{
				f[j]=-1,g[j]=0;
				continue;
			}
			if(f[j-wei[u]]!=-1)
			{
				f[j]=f[j-wei[u]]+val[u];
				g[j]=g[j-wei[u]];
			}
			else f[j]=-1,g[j]=0;
		}
		for(int i=head[u];i;i=nxt[i])
		{
			int v=to[i];
			if(v==fa) continue;
			DP(v,u);
		}
		if(u!=rt1&&u!=rt2)
		{
			for(int j=0;j<=m;j++)
			{
				if(ff[u][j]>f[j])
				{
					f[j]=ff[u][j];
					g[j]=gg[u][j];
				}
				else if(ff[u][j]==f[j])
					g[j]+=gg[u][j];
			}
		}
	}
	pll calc(int _rt1,int _rt2)
	{
		rt1=_rt1,rt2=_rt2;
		memset(f,-1,sizeof(f));
		memset(g,0,sizeof(g));
		if(1ll*dis[rt1][rt2]*max(val[rt1],val[rt2])>lim) return mk(-1,114514);
		f[0]=0,g[0]=1;
		DP(rt1,0);
		ll nmax=0,ans=0;
		for(int j=0;j<=m;j++)
			nmax=max(nmax,f[j]);
		for(int j=0;j<=m;j++)
			if(nmax==f[j]) ans+=g[j];
		return mk(nmax,ans);
	}
}

namespace Binom
{
	typedef vector<ll> poly;
	poly operator * (poly a,poly b)
	{
		int sa=a.size(),sb=b.size();
		poly c(min(sa+sb-1,23));
		for(int i=0;i<sa;i++)
			for(int j=0;j<sb&&i+j<23;j++)
				Add(c[i+j],mul(a[i],b[j]));
		return c;
	}
	ll powp[25],C[25][25];
	void init()
	{
		powp[0]=1;
		for(int i=1;i<23;i++)
			powp[i]=powp[i-1]*5;
		C[0][0]=1;
		for(int n=1;n<23;n++)
		{
			C[n][0]=1;
			for(int m=1;m<=n;m++)
				C[n][m]=add(C[n-1][m-1],C[n-1][m]);
		}
	}
	ll getg(ll n)
	{
		ll ans=0;
		while(n)
		{
			ans+=n/5;
			n/=5;
		}
		return ans;
	}
	poly geth(ll n)
	{
		if(!n) return poly{1};
		ll pp=n/10*10/2;
		poly hp1=geth(pp),hp2(hp1.size());
		for(int i=0,s=hp1.size();i<s;i++)
		{
			ll powpp=1;
			for(int j=i;j<s;j++,Mul(powpp,pp))
				Add(hp2[i],mul(C[j][i],mul(hp1[j],powpp)));
		}
		hp1=hp1*hp2;
		for(ll i=(pp<<1)+1;i<=n;i++)
			if(i%5) hp1=hp1*poly{i,1};
		return hp1;
	}
	ll getf(ll n)
	{
		if(!n) return 1;
		return mul(getf(n/5),geth(n)[0]);
	}
	ll calc(ll n)
	{
		if(n<k) return 0;
		ll f1=getf(n),f2=getf(k),f3=getf(n-k);
		ll g1=getg(n),g2=getg(k),g3=getg(n-k);
		if(g1-g2-g3>=23) return 0;
		ll phipk=mod-mod/5;
		ll inv2=poww(f2,phipk-1);
		ll inv3=poww(f3,phipk-1);
		return mul(mul(f1,mul(inv2,inv3)),powp[g1-g2-g3]);
	}
}

int main()
{
	scanf("%d%d%d%lld",&n,&m,&k,&lim);
	for(int i=1;i<=n;i++) wei[i]=read();
	for(int i=1;i<=n;i++) val[i]=read();
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read(),w=read();
		Tree::adde(u,v,w),Tree::adde(v,u,w);
	}
	Tree::init();
	Binom::init();
	ll llim=lim;
	lim=LNF;
	for(int i=1;i<=n;i++)
		Sval=max(Sval,Tree::calc(i,0).fi);
	lim=llim;
	ll ans=0;
	for(int i=1;i<=n;i++)
	{
		pll now=Tree::calc(i,0);
		if(now.fi==Sval) Add(ans,Binom::calc(now.se));
	}
	for(int i=2;i<=Tree::cnt;i+=2)
	{
		int u=Tree::to[i],v=Tree::to[i^1];
		pll now=Tree::calc(u,v);
		if(now.fi==Sval) Dec(ans,Binom::calc(now.se));
	}
	printf("%lld\n",ans);
	return 0;
}
posted @   ez_lcw  阅读(53)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示