1 条题解
-
0
要解决在树上选择包含所有关键边的简单路径数量的问题,我们可以通过以下思路和代码实现:
问题分析
题目要求在一棵n个节点的树中,找到所有包含k条关键边的简单路径(即不重复边的路径)的数量。关键边是指被特别标记的边,路径必须经过所有这些关键边才能被计数。
核心思路
- 树的表示与关键边标记:将树用邻接表表示,标记关键边。
- 虚节点转换:将每条关键边转换为一个虚节点,便于处理边的选择和路径计数。
- 动态规划(DP)与组合计数:使用DP计算子树中路径的方案数,结合组合数(阶乘、逆元)处理节点度数和路径选择。
- 分治与并查集(Union-Find):通过分治(如重心分解)和并查集处理虚节点的连通性,确保路径包含所有关键边。
代码实现(以GD-0001为例)
#include<bits/stdc++.h> using namespace std; typedef long long ll; int read(){int x=0,f=1;char ch;for(;!isdigit(ch=getchar());)if(ch=='-')f=-1;for(;isdigit(ch);ch=getchar())x=x*10+(ch-'0');return x*f;} namespace Main { const int N=200010,mod=1e9+7;int ksm(int x,int y=mod-2){int res=1;for(;y;y>>=1,x=1ll*x*x%mod)if(y&1)res=1ll*res*x%mod;return res;} int n,m,q,k,ans,f[N],g[N],h[N],fa[N],head[N],d[N],fac[N],to[N<<1],nxt[N<<1],tot,x,y,z,xx,yy,zz,res,suf[N];bool fl[N],vis[N];vector<int>c; void link(int x,int y){to[++tot]=y,++d[y],nxt[tot]=head[x],head[x]=tot;}void init(){for(int i=fac[0]=1;i<N;++i)fac[i]=1ll*fac[i-1]*i%mod;} namespace ust{int fa[N],sz[N];int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}void merge(int x,int y){y=find(y),fa[x=find(x)]=y,sz[y]+=sz[x];}void init(){for(int i=1;i<m;++i)fa[i]=i,sz[i]=(d[i]==1);}bool Q(int x){return sz[find(x)]>1;}} void dfs0(int x){for(int i=head[x];i;i=nxt[i])if(to[i]!=fa[x])fa[to[i]]=x,dfs0(to[i]);if(x<=n){f[fa[x]]=fac[d[x]-1];for(int i=head[x];i;i=nxt[i])if(to[i]!=fa[x])f[fa[x]]=1ll*f[fa[x]]*f[to[i]]%mod;}} struct cal{int s,cnt;void clr(){s=1,cnt=0;}void ins(int x){if(x)s=1ll*s*x%mod;else ++cnt;}int qry(int x){if(cnt>!x)return 0;return x?(1ll*s*ksm(x)%mod):s;}}A; void dfs1(int x){if(x<=n){A.clr(),A.ins(fac[d[x]-1]);if(fa[x])A.ins(g[fa[x]]);for(int i=head[x];i;i=nxt[i])if(to[i]!=fa[x])A.ins(f[to[i]]);for(int i=head[x];i;i=nxt[i])if(to[i]!=fa[x])g[to[i]]=A.qry(f[to[i]]);}for(int i=head[x];i;i=nxt[i])if(to[i]!=fa[x])dfs1(to[i]);} int calc(){int res=0;for(int i=n+1;i<m;++i)res=(res+1ll*f[i]*g[i])%mod;for(int i=1;i<=n;++i)if(d[i]>1){z=1ll*(fa[i]?g[fa[i]]:1)*fac[d[i]-2]%mod;for(int j=head[i];j;j=nxt[j])if(to[j]!=fa[i])z=1ll*z*f[to[j]]%mod;res=(res-(d[i]-1ll)*d[i]/2%mod*z)%mod;}res=(res+mod)%mod;return res;} void dfs2(int x){vis[x]=1;for(int i=head[x];i;i=nxt[i])if(to[i]!=fa[x]&&(to[i]<=n||fl[to[i]]))dfs2(to[i]);if(x<=n){if(d[x]==1&&fl[fa[x]])return h[fa[x]]=1,void();c.clear(),zz=1;for(int i=head[x];i;i=nxt[i])if(to[i]!=fa[x]){if(fl[to[i]])c.emplace_back(to[i]);else zz=1ll*zz*f[to[i]]%mod;}q=(int)c.size();int res=0,s=1,z=0;suf[q]=1;for(int i=q;i--;)suf[i]=1ll*suf[i+1]*f[c[i]]%mod;for(int i=0;i<q;++i)res=(res+1ll*h[c[i]]*suf[i+1]%mod*z)%mod,z=(1ll*z*f[c[i]]+1ll*s*h[c[i]])%mod,s=1ll*s*f[c[i]]%mod;z=1ll*z*zz%mod,res=1ll*res*zz%mod;if(d[x]>1)z=1ll*z*fac[d[x]-2]%mod,res=1ll*res*fac[d[x]-2]%mod;if(fa[x])h[fa[x]]=z;if(d[x]==1)res+=z;ans=(ans-1ll*(fa[x]?g[fa[x]]:1)*res)%mod;}} void cal(int x){for(;fl[fa[x]];x=fa[fa[x]]);dfs2(x);} void main() { cin>>n>>k,m=n*2,memset(fl,1,sizeof(fl)),memset(vis,0,sizeof(vis)),memset(d,0,sizeof(d)),memset(head,0,sizeof(head)),memset(f,0,sizeof(f)),memset(g,0,sizeof(g)),tot=0;for(int i=n+1;i<m;++i)cin>>x>>y,link(x,i),link(i,x),link(y,i),link(i,y);dfs0(1),dfs1(1);ans=calc(),ust::init();for(;k--;)cin>>x,fl[x+n]=0;fl[0]=0,memset(h,0,sizeof(h));for(int i=n+1;i<m;++i)if(fl[i])for(int j=head[i];j;j=nxt[j])ust::merge(to[j],i);for(int i=1;i<=n;++i)if(!vis[i]&&ust::Q(i))cal(i);cout<<(ans+mod)%mod<<endl; } } int main() { freopen("traverse.in","r",stdin); freopen("traverse.out","w",stdout); int T,c;ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);cin>>c>>T,Main::init();while(T--)Main::main(); return 0; }代码说明
- 虚节点转换:将每条关键边转换为虚节点,通过
link函数连接原始节点与虚节点,便于路径计数。 - DFS遍历与DP:
dfs0和dfs1计算子树中路径的组合数,A结构体处理子树状态的乘积与计数。 - 并查集:
ust命名空间的并查集用于处理虚节点的连通性,确保路径包含所有关键边。 - 组合数计算:
fac数组存储阶乘,ksm计算逆元,处理组合计数和模运算。 - 关键边处理:通过
fl数组标记关键边,dfs2和cal函数计算包含所有关键边的路径数量,最终结果由calc函数调整并输出。
总结
该代码通过将边转换为虚节点、动态规划、并查集和组合计数,高效计算了包含所有关键边的简单路径数量,并处理了多组测试用例。
- 1
信息
- ID
- 2559
- 时间
- 1000ms
- 内存
- 512MiB
- 难度
- 10
- 标签
- 递交数
- 8
- 已通过
- 2
- 上传者