【LOJ2462】【2018 集训队互测 Day 1】完美的集合(容斥,树上连通块DP,exLucas)
点数-边数第二次见了(第一次是在十二省联考-希望),听说是树上连通块计数问题的一种经典方法?
考虑对于一个树上连通块来说,能对所有点进行测试的点(以下称为 “合法点”)肯定也构成了一个连通块。
由于选出的 个连通块有交,所以它们的并也是一个树上连通块,那么这个并中的合法点也构成了一个连通块。
考虑容斥,一般的容斥的形式是 ”钦定某几个点为合法点“,但注意到合法点一定构成连通块,所以可以用 ”钦定某个点为合法点“ 的选取方案数减去 ”钦定某条边的两个端点都为合法点“ 的选取方案数,这样每一种选取方案都恰好被统计了一次。
现在考虑求出某个点 为合法点的完美集合个数,然后再在里面选出 个的方案数即为钦定 为合法点的选取方案数。
先找到能被 测试的点集,那么现在就是问这个点集中有多少个包含 的子集为完美集合。
先把 提作为根,直接自底向上背包合并的复杂度为 的。
延续之前的套路,我们不考虑合并,而是考虑逐个添加物品,在 dfs 序上转移,时间复杂度降至 。
注意过程中我们不能将 DP 值 取模,因为我们最后要求的是 。但你发现 DP 值其实是可以存的下的,因为 ,那么 。
现在的问题是多次求 的结果,其中 , 固定。
使用 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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?