题解 AT5042 Edge Ordering

题意:给定一张 n 个点 m 条边的无向图,前 n1 条边构成一个生成树。现在要给 m 条边分别赋值 1,2,...,m,使得前 n1 条边构成一个最小生成树。求方案数,模 109+7

先想暴力。全排列枚举前 n1 条边的顺序,接下来容易发现是每条非树边都有个要求,是要在某条树边之后才能加入。考虑倒着加边。可以转化为:一个空序列,每次会收到一个黑/白球,黑球只能插入到序列开头,白球可以插入到任意位置。黑球的位置之和对应原题答案。

我们记录四元组 (n,b,c,s) 表示 n 个球,b 个黑球,方案数 c,黑球位置和 s。容易转移:

  • 加入白球:(n,b,c,s)(n+1,b,(n+1)c,(n+2)s)
  • 加入多个白球同理可推,是带个阶乘的。
  • 加入黑球:(n,b,c,s)(n+1,b+1,c,s+(b+1)c)

这样就可以 O(n!) 做了。然而过不去,考虑状压。

我们先预处理对于每一个边集 S(当前倒着加入的边),有几条非树边是可以加入的。也就是此时白球的数量。容易发现每条非树边对应树上的一条路径,这个路径上任何一条边存在,这个非树边都不能加入。于是可以 FWT 或者高维前缀和解决了。

接下来按照上面的转移做状压 DP 就好了。复杂度是 O(nm+n2n)

// 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;
}

posted @   Little09  阅读(34)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
历史上的今天:
2021-06-30 题解 P4025 [PA2014]Bohater
点击右上角即可分享
微信分享提示