题解 AT5042 Edge Ordering
题意:给定一张 个点 条边的无向图,前 条边构成一个生成树。现在要给 条边分别赋值 ,使得前 条边构成一个最小生成树。求方案数,模 。
先想暴力。全排列枚举前 条边的顺序,接下来容易发现是每条非树边都有个要求,是要在某条树边之后才能加入。考虑倒着加边。可以转化为:一个空序列,每次会收到一个黑/白球,黑球只能插入到序列开头,白球可以插入到任意位置。黑球的位置之和对应原题答案。
我们记录四元组 表示 个球, 个黑球,方案数 ,黑球位置和 。容易转移:
- 加入白球:
- 加入多个白球同理可推,是带个阶乘的。
- 加入黑球:
这样就可以 做了。然而过不去,考虑状压。
我们先预处理对于每一个边集 (当前倒着加入的边),有几条非树边是可以加入的。也就是此时白球的数量。容易发现每条非树边对应树上的一条路径,这个路径上任何一条边存在,这个非树边都不能加入。于是可以 FWT 或者高维前缀和解决了。
接下来按照上面的转移做状压 DP 就好了。复杂度是 。
// By: Little09
// Problem: AT5042 Edge Ordering
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/AT5042
// Memory Limit: 1000 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod=1e9+7;
const int N=408,M=21,S=(1<<M);
int res,n,m;
ll dp[S],ans[S][4];
int a[N],b[N];
int h[N],nxt[N],cnt,v[N],t[N];
void add(int x,int y,int z)
{
t[++cnt]=y;
v[cnt]=z;
nxt[cnt]=h[x];
h[x]=cnt;
}
void dfs(int x,int f,int y,int dis)
{
if (y==x)
{
res=dis;
return;
}
for (int i=h[x];i;i=nxt[i])
{
if (t[i]==f) continue;
dfs(t[i],x,y,(dis|(1<<(v[i]))));
}
}
ll ksm(ll x,ll y)
{
ll res=1;
while (y)
{
if (y&1) res=res*x%mod;
x=x*x%mod;
y>>=1;
}
return res;
}
ll jc[N+5],inv[N+5];
void init()
{
jc[0]=1;
for (int i=1;i<=N;i++) jc[i]=jc[i-1]*i%mod;
inv[N]=ksm(jc[N],mod-2);
for (int i=N-1;i>=0;i--) inv[i]=inv[i+1]*(i+1)%mod;
}
inline int read()
{
int F=1,ANS=0;
char C=getchar();
while (C<'0'||C>'9')
{
if (C=='-') F=-1;
C=getchar();
}
while (C>='0'&&C<='9')
{
ANS=ANS*10+C-'0';
C=getchar();
}
return F*ANS;
}
int main()
{
init();
n=read(),m=read();
for (int i=1;i<n;i++)
{
int x=read(),y=read();
a[i]=x,b[i]=y;
add(x,y,i-1),add(y,x,i-1);
}
int s=(1<<(n-1))-1;
for (int i=n;i<=m;i++)
{
int x=read(),y=read();
dfs(x,0,y,0);
//cout << res << endl;
dp[s-res]++;
}
for (int i=0;i<n-1;i++)
{
for (int j=(1<<(n-1))-1;j>=0;j--)
{
if (!(j&(1<<i))) dp[j]=(dp[j]+dp[j^(1<<i)])%mod;
}
}
ans[0][0]=0,ans[0][1]=0,ans[0][2]=1,ans[0][3]=0;
for (int i=1;i<=s;i++)
{
int u=i;
ans[i][1]=__builtin_popcount(i);
ans[i][0]=ans[i][1]+m-n+1-dp[i];
while (u)
{
int v=(u&(-u)),w=(i^v);
u^=v;
int l=dp[w]-dp[i];
ans[i][2]=(ans[i][2]+ans[w][2]*inv[ans[w][0]]%mod*jc[ans[w][0]+l]%mod)%mod;
ans[i][3]=(ans[i][3]+ans[w][3]*inv[ans[w][0]+1]%mod*jc[ans[w][0]+l+1]%mod)%mod;
}
ans[i][3]=(ans[i][3]+ans[i][1]*ans[i][2]%mod)%mod;
}
cout << ans[s][3];
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2021-06-30 题解 P4025 [PA2014]Bohater