2025牛客暑期多校训练营5 K.Perfect Journey

2025牛客暑期多校训练营5 K题题解

前置知识:快速莫比乌斯变换(FMT)与卷积 - Linear_L - 博客园

K-Perfect Journey_2025牛客暑期多校训练营5

题意

给出一颗含有 $ n $ 个节点的树, $ m $ 条关键边以及 $ k $ 条树上路径,你需要从中选出最少的路径使得所有关键边都被经过,输出最少选几条和方案数。

思路

由于给出的 $ m $ 很小,考虑将关键边集合进行状压,那么每条树上路径都可以用一个状压值来代替。那么问题就转换成:给定一个集合 $ S={x_1,x_2,\ldots,x_m } $ 以及一个 $ S $ 上的集合组 $ F={S_0,S_1,\ldots,S_{k-1}} $ ,我们定义一个覆盖$ C $ 是 $ F $ 的一个子族,求 $ C $ 中集合的并为 $ S $ 且包含集合数最少的覆盖个数。

那么这个题就和 #153. 集合覆盖计数 - 题目 - LibreOJ 没有太大的差别了,我们先处理 loj 上的题。

loj. 153 集合覆盖计数

原题链接:#153. 集合覆盖计数 - 题目 - LibreOJ

不妨令 $ dp_{i,S} $ 表示选择了 $ i $ 个集合,且这 $ i $ 个集合的并为 $ S $ 的方案数,考虑转移方程:

\[dp_{i,S}=\sum\limits_{A \cup B=S}dp_{i-1,A}\times dp_{1,B}-(i-1)\times dp_{i-1,S} \]

下面我们详细解释一下这个转移方程:

对于 $ \sum\limits_{A \cup B}dp_{i-1,A} \times dp_{1,B} $ 表示考虑当前的某个集合的选择带来的方案数更新(其中要求 $ A \cup B=S $),但是考虑到在状态 $ dp_{i-1,A} $ 选择的 $ i-1 $ 个集合中我们可能包含当前考虑的这个集合,所以需要进行去重操作,那么当前面选择的 $ i-1 $ 个包含当前考虑的这个集合且集合的并为 $ S $ 时,此时 $ dp_{i-1,A} $ 中的集合 $ A $ 就一定等于 $ S $ ,证明显然。

我们将式子进行变形:

\[dp_{i,S}+(i-1)\times dp_{i-1,S}=\sum\limits_{A \cup B=S}dp_{i-1,A} \times dp_{1,B} \]

不妨令 $ f_{i,S}=dp_{i,S}+(i-1)\times dp_{i-1,S} $ ,那么有:

\[f_{i,S}=\sum\limits_{A \cup B =S} dp_{i-1,A} \times dp_{1,B} \]

我们会发现等式是符合一个卷积形式的。

不妨令:

\[\hat{f}_{i,S}=\sum\limits_{T \subseteq S}f_{i,T},\hat{dp}_{i,S}=\sum\limits_{T \subseteq S}dp_{i,T} \]

不难看出有莫比乌斯变换:

\[\hat{f}_{i,S}=\hat{dp}_{i-1,S}\times \hat{dp}_{1,S} \]

再考虑函数 $ f $ :

\[\begin{align} \hat{f}_{i,S}=&\sum\limits_{T \subseteq S}f_{i,T}\\ =&\sum\limits_{T \subseteq S}dp_{i,T}+(i-1)\times dp_{i-1,T}\\ =&\sum\limits_{T \subseteq S}dp_{i,T}+(i-1)\times \sum\limits_{T \subseteq S}dp_{i-1,T}\\ =&\hat{dp}_{i,S}+(i-1)\times \hat{dp}_{i-1,S} \end{align} \]

那么

\[\hat{dp}_{i,S}=\hat{dp}_{i-1,S}\times\hat{dp}_{1,S}-(i-1)\times\hat{dp}_{i-1,S} \]

会发现满足一个递推式,我们只需要求出 $ \hat{dp}_{1,S}$ 即可。

我们可以枚举选择的集合个数 $ i $ ,然后进行莫比乌斯变换点乘后再进行反演,得到当前的 $ dp_{i,S}(S=2^n-1) $ 贡献给最后的答案。时间复杂度 $ O(k\times n \times 2^n) $ ,对于本题数据无法通过。

我们会发现每循环枚举一次集合个数都进行一次莫比乌斯反演是时间复杂度瓶颈,考虑优化。

现在给定 $ k $ ,我们设 $ S=2^n-1 $ ,最终的答案就是求 $ \sum\limits_{i=1}^kdp_{i,S} $ ,不妨令 $ g_S= \sum\limits_{i=1}^kdp_{i,S} $同样的,我们考虑进行莫比乌斯变换:

\[\begin{align} \hat{g}_S=&\sum\limits_{T \subseteq S}\sum\limits_{i=1}^kdp_{i,T}\\ =&\sum\limits_{i=1}^k\sum\limits_{T \subseteq S}dp_{i,T}\\ =&\sum\limits_{i=1}^k\hat{dp}_{i,S} \end{align} \]

我们只需要统计对于给定并集 $ X $ 各个集合个数 $ i $ 的加和 $ \sum\limits_{i=1}^k\hat{dp}_{i,X} $ ,然后最后统一做一次莫比乌斯反演即可。

代码:

#include<bits/stdc++.h>
//#define int long long
#define endl '\n'
using namespace std;

#define bitcnt(x) __builtin_popcount(x)
const int mod=998244353;
const int maxn=2e5+10;
int n,m,k,len,invit[maxn];
int dp[2][1<<22],term[1<<22],fac[maxn],invfac[maxn],fuck[1<<22];

int fast_pow(int a,int b,int mod)
{
	int res=1;
	a%=mod;
	while(b)
	{
		if(b&1) res=(1ll*res*a)%mod;
		a=(1ll*a*a)%mod;
		b>>=1;
	}
	return res;
} 

int inv(int a,int mod)
{
	return fast_pow(a,mod-2,mod);
}

void FMT(int *f,int n)
{
    for(int i=0;i<n;++i)
        for(int j=0;j<(1<<n);++j)
            if(j&(1<<i)) f[j]=(1ll*f[j]+f[j^(1<<i)])%mod;
}
void IFMT(int *f,int n)
{
    for(int i=0;i<n;++i)
        for(int j=0;j<(1<<n);++j)
            if(j&(1<<i)) f[j]=((1ll*f[j]-f[j^(1<<i)])%mod+mod)%mod;
} 

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	
	int ans=0;
	cin>>n>>m>>k;
	fac[0]=1;
	for(int i=1;i<=k;i++) fac[i]=(1ll*fac[i-1]*i)%mod;
	for(int i=1;i<=k;i++) invfac[i]=inv(fac[i],mod);
	len=1<<n;
	for(int i=0;i<m;i++)
	{
		int x;
		cin>>x;
		dp[1][x]=(1ll*dp[1][x]+1)%mod;
	}
	FMT(dp[1],n);
	for(int i=0;i<len;i++) term[i]=fuck[i]=dp[1][i];
	int now=1;
	int old=0;
	for(int i=2;i<=k;i++)
	{
		swap(now,old);
		for(int s=0;s<len;s++)
		{
			dp[now][s]=(1ll*dp[old][s]*fuck[s]-(1ll*(i-1)*dp[old][s])%mod+mod)%mod;
			term[s]=(term[s]+(1ll*dp[now][s]*invfac[i])%mod)%mod; 
		}
	}
	IFMT(term,n);
	cout<<term[len-1]<<endl;
	
	return 0;
 } 

我们回到牛客暑期多校那道相似的题。

会发现题目与上述题不同,只需要求出最小覆盖即可。我们在枚举集合个数 $ i $、做点乘的过程中只要发现 $ dp_{i,S}$ (其中 $ S $ 为元素全集)不为 $ 0 $ ,那么 $ i,dp_{i,S} $ 就是最后的答案。我们不需要每一次都进行一次莫比乌斯反演,只需要根据莫比乌斯反演的定义式:

\[g(S)=\sum\limits_{T \subseteq S}(-1)^{|S|-|T|} f(T) \]

进行时间复杂度 $ O(2^m) $ 的加和求解判断最后答案是否为 $ 0 $ 即可。

代码:

#include<bits/stdc++.h>
//#define int long long
#define endl '\n'
using namespace std;

const int mod=998244353;
const int maxn=2e5+10;
int n,m,k;
vector<int>ds[maxn];
int d[maxn],f[1<<22],dp[2][1<<22],len,fac[maxn],invit[maxn],sh[1<<22];
vector<pair<int,int> >term;
map<pair<int,int>,int>mp;

int fast_pow(int a,int b,int mod)
{
	int res=1;
	a%=mod;
	while(b)
	{
		if(b&1) res=(1ll*res*a)%mod;
		a=(1ll*a*a)%mod;
		b>>=1;
	}
	return res;
}

int inv(int a,int mod)
{
	return fast_pow(a,mod-2,mod);
}

void dfs(int u,int fa)
{
	for(auto v:ds[u])
	{
		if(v==fa) continue;
		d[v]=d[u]|mp[make_pair(u,v)];
		dfs(v,u);
	}
}

void FMT(int *f,int n)
{
    for(int i=0;i<n;++i)
        for(int j=0;j<(1<<n);++j)
            if(j&(1<<i)) f[j]=(1ll*f[j]+f[j^(1<<i)])%mod;
}
void IFMT(int *f,int n)
{
    for(int i=0;i<n;++i)
        for(int j=0;j<(1<<n);++j)
            if(j&(1<<i)) f[j]=((1ll*f[j]-f[j^(1<<i)])%mod+mod)%mod;
} 

int work(int *f)
{
	int fuck=0;
	for(int s=0;s<len;s++)
	{
		int much=__builtin_parity(s^((1<<m)-1));
		if(much&1) fuck=(fuck-f[s]+mod)%mod;
		else fuck=(fuck+f[s])%mod; 
	}
	return fuck;
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	
	cin>>n>>m>>k;
	for(int i=1;i<=n-1;i++)
	{
		int u,v;
		cin>>u>>v;
		ds[u].push_back(v);
		ds[v].push_back(u);
		term.push_back(make_pair(u,v));
	}
	for(int i=1;i<=m;i++)
	{
		int id;
		cin>>id;
		int u=term[id-1].first;
		int v=term[id-1].second;
		mp[make_pair(u,v)]=1ll<<i-1;
		mp[make_pair(v,u)]=1ll<<i-1;
	}
	dfs(1,0);
	for(int i=1;i<=k;i++)
	{
		int u,v;
		cin>>u>>v;
		int now=d[u]^d[v];
		dp[1][now]++;
	}
	if(dp[1][(1<<m)-1])
	{
		cout<<1<<' '<<dp[1][(1<<m)-1]<<endl;
		return 0;
	}
	len=1<<m;
	FMT(dp[1],m);
	for(int i=0;i<len;i++) f[i]=dp[1][i];
	int now=1;
	int old=0;
	fac[0]=1;
	for(int i=1;i<=min(k,m);i++) fac[i]=(1ll*fac[i-1]*i)%mod;
	for(int i=1;i<=min(k,m);i++) invit[i]=inv(fac[i],mod);
	for(int i=2;i<=min(k,m);i++)
	{
		swap(old,now);
		for(int s=0;s<len;s++)
		{
			dp[now][s]=((1ll*dp[old][s]*f[s])%mod-(1ll*(i-1)*dp[old][s])%mod+mod)%mod;
			sh[s]=dp[now][s];
		}
		int ans=work(sh);
		if(ans)
		{
			cout<<i<<' '<<(1ll*ans*invit[i])%mod<<endl;
			return 0;
		}
	}
	cout<<-1<<endl;
	
	
	return 0;
 } 
posted on 2025-08-03 18:55  Linear_L  阅读(50)  评论(1)    收藏  举报