洛谷 P4841 城市规划(分治FFT+容斥)

本文探讨了在无向图中,对于给定的n个节点,计算所有可能的联通图方案数的问题。通过引入g_n和f_n两个辅助序列,利用组合数学和分治FFT算法,成功解决了这一复杂问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接
推一波式子
首先如果不要求联通的话公式很简单,令n个点不要求联通的方案数为 g n g_n gn
g n = 2 n ∗ ( n − 1 ) 2 g_n=2^{\frac{n*(n-1)}{2}} gn=22n(n1)
因为每条边都可以选或不选
然后假设要求联通的方案数为 f n f_n fn
可以找出 g n g_n gn f f f的关系
g n = ∑ i = 1 n ( n − 1 i − 1 ) f i g n − i g_n=\sum_{i=1}^n {n-1\choose i-1} f_ig_{n-i} gn=i=1n(i1n1)figni
相当于枚举第一个点所在的联通块大小为i,然后乘以其它点随便连的方案数,如果不理解请再看一下g和f的定义
现在我们要求 f n f_n fn所以拆一下式子
g n = f n + ∑ i = 1 n − 1 ( n − 1 i − 1 ) f i g n − i g_n=f_n+\sum_{i=1}^{n-1} {n-1\choose i-1} f_ig_{n-i} gn=fn+i=1n1(i1n1)figni
f n = g n − ∑ i = 1 n − 1 ( n − 1 i − 1 ) f i g n − i f_n=g_n-\sum_{i=1}^{n-1} {n-1\choose i-1} f_ig_{n-i} fn=gni=1n1(i1n1)figni
f n = g n − ∑ i = 1 n − 1 ( n − 1 ) ! ( i − 1 ) ! ( n − i ) ! f i g n − i f_n=g_n-\sum_{i=1}^{n-1} \frac{(n-1)!}{(i-1)!(n-i)!} f_ig_{n-i} fn=gni=1n1(i1)!(ni)!(n1)!figni
f n = g n − ( n − 1 ) ! ∑ i = 1 n − 1 f i ( i − 1 ) ! g n − i ( n − i ) ! f_n=g_n-(n-1)!\sum_{i=1}^{n-1} \frac{f_i}{(i-1)!} \frac{g_{n-i}}{(n-i)!} fn=gn(n1)!i=1n1(i1)!fi(ni)!gni
后面这个式子看着像卷积
∑ i = 1 n − 1 f i ( i − 1 ) ! g n − i ( n − i ) ! \sum_{i=1}^{n-1} \frac{f_i}{(i-1)!} \frac{g_{n-i}}{(n-i)!} i=1n1(i1)!fi(ni)!gni
h i = f i ( i − 1 ) ! h_i=\frac{f_i}{(i-1)!} hi=(i1)!fi t i = g i i ! t_i=\frac{g_i}{i!} ti=i!gi代入得
( n − 1 ) ! h n = n ! t n − ( n − 1 ) ! ∑ i = 1 n − 1 h i t n − i (n-1)!h_n=n!t_n-(n-1)!\sum_{i=1}^{n-1} h_i t_{n-i} (n1)!hn=n!tn(n1)!i=1n1hitni
h n = n t n − ∑ i = 1 n − 1 h i t n − i h_n=nt_n-\sum_{i=1}^{n-1} h_i t_{n-i} hn=ntni=1n1hitni
然后分治FFT就完事了
顺便一提,这题的原根也是3

代码如下:

#include<bits/stdc++.h>
#define mod 1004535809
#define gg 3
using namespace std;

int r[400010];
long long tmp1[400010],tmp2[400010],h[400010],t[400010];
long long fac[400010],inv[400010];

long long kasumi(long long a,long long b)
{
	long long ans=1;
	while(b)
	{
		if(b&1) ans=ans*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return ans;
}

void NTT(long long *a,int kd,int logn)
{
	int lim=(1<<logn);
	for(int i=0;i<lim;i++)
	{
		r[i]=(r[i>>1]>>1)|((i&1)<<(logn-1));
	}
	for(int i=0;i<lim;i++)
	{
		if(i<r[i]) swap(a[i],a[r[i]]);
	}
	for(int mid=1;mid<lim;mid<<=1)
	{
		long long wn=kasumi(gg,(mod-1)/(mid<<1));
		if(kd) wn=kasumi(wn,mod-2);
		for(int i=0;i<lim;i+=(mid<<1))
		{
			long long w=1;
			for(int j=0;j<mid;j++,w=w*wn%mod)
			{
				long long x=a[i+j];
				long long y=a[i+j+mid]*w%mod;
				a[i+j]=(x+y)%mod;
				a[i+j+mid]=(x-y+mod)%mod;
			}
		}
	}
	if(kd)
	{
		int invl=kasumi(lim,mod-2);
		for(int i=0;i<lim;i++)
		{
			a[i]=a[i]*invl%mod;
		}
	}
}

void solve(int l,int r,int logn)
{
	if(!logn) return ;
	int mid=(l+r)>>1;
	solve(l,mid,logn-1);
	for(int i=l;i<=r;i++)
	{
		tmp2[i-l]=t[i-l];
	}
	for(int i=l;i<=mid;i++)
	{
		tmp1[i-l]=h[i];
	}
	for(int i=mid+1;i<=r;i++)
	{
		tmp1[i-l]=0;
	}
	NTT(tmp1,0,logn);
	NTT(tmp2,0,logn);
	for(int i=l;i<=r;i++)
	{
		tmp1[i-l]=tmp1[i-l]*tmp2[i-l]%mod;
	}
	NTT(tmp1,1,logn);
	for(int i=mid+1;i<=r;i++)
	{
		h[i]=(h[i]-tmp1[i-l]+mod)%mod;
	}
	solve(mid+1,r,logn-1);
}

void init()
{
	fac[0]=1;
	for(int i=1;i<=200000;i++)
	{
		fac[i]=fac[i-1]*i%mod;
	}
	inv[200000]=kasumi(fac[200000],mod-2);
	for(int i=199999;i>=0;i--)
	{
		inv[i]=inv[i+1]*(i+1)%mod;
	}
	t[0]=1;
	long long tmp;
	for(int i=1;i<=200000;i++)
	{
		tmp=kasumi(2,1ll*(i-1)*i/2);
		t[i]=tmp*inv[i]%mod;
		h[i]=t[i]*i%mod;
	}
}

int n;

int main()
{
	int cnt=1;
	init();
	scanf("%d",&n);
	for(;(1<<cnt)<=n;cnt++);
	solve(0,(1<<cnt)-1,cnt);
	printf("%lld\n",h[n]*fac[n-1]%mod);
}
### 洛谷 FFT 题目与解法 洛谷平台上的 FFT(快速傅里叶变换)相关题目通常涉及多项式乘法、卷积计算以及字符串匹配等场景。以下是关于 FFT 的一些常见题型及解法的总结[^1]。 #### 1. 多项式乘法 FFT 最常见的应用之一是加速多项式的乘法运算。给定两个多项式 $A(x)$ 和 $B(x)$,直接相乘的时间复杂度为 $O(n^2)$,而使用 FFT 可以将时间复杂度优化到 $O(n \log n)$。以下是一个基于 FFT 的多项式乘法代码示例[^2]: ```cpp #include <iostream> #include <complex> #include <vector> using namespace std; typedef complex<double> cp; void FFT(vector<cp> &a, int n, int inv) { for(int i=0, j=0; i<n; ++i){ if(i > j) swap(a[i], a[j]); for(int k = n >> 1; (j ^= k) < k; k >>= 1); } for(int len=2; len<=n; len<<=1){ double ang = inv * 2 * acos(-1) / len; cp wlen(cos(ang), sin(ang)); for(int i=0; i<n; i+=len){ cp w(1, 0); for(int j=0; j<len/2; ++j){ cp u = a[i+j], v = a[i+j+len/2] * w; a[i+j] = u + v; a[i+j+len/2] = u - v; w *= wlen; } } } if(inv == -1){ for(auto &x : a) x /= n; } } vector<double> multiply(const vector<double> &a, const vector<double> &b){ int n = 1; while(n < (int)a.size() + (int)b.size()) n <<= 1; vector<cp> fa(a.begin(), a.end()), fb(b.begin(), b.end()); fa.resize(n); fb.resize(n); FFT(fa, n, 1); FFT(fb, n, 1); for(int i=0; i<n; ++i) fa[i] *= fb[i]; FFT(fa, n, -1); vector<double> res(n); for(int i=0; i<n; ++i) res[i] = fa[i].real(); return res; } ``` #### 2. 字符串匹配 FFT 还可以用于字符串匹配问题,例如通过将字符串转化为数值序列后进行卷积计算。以下是字符串匹配的一个简单实现[^2]: ```cpp #include <bits/stdc++.h> using namespace std; typedef complex<double> cp; const double PI = acos(-1); void fft(vector<cp> &a, bool inv){ int n = a.size(); for(int i=1,j=0;i<n-1;i++){ for(int k=n>>1;k>(j^=k);k>>=1); if(i<j) swap(a[i],a[j]); } for(int len=2;len<=n;len<<=1){ double ang = 2*PI/len*(inv?-1:1); cp wlen(cos(ang),sin(ang)); for(int i=0;i<n;i+=len){ cp w(1,0); for(int j=0;j<len/2;j++){ cp u = a[i+j], v = a[i+j+len/2]*w; a[i+j] = u+v; a[i+j+len/2] = u-v; w *= wlen; } } } if(inv){ for(auto &x:a) x/=n; } } int main(){ string s1,s2; cin >> s1 >> s2; int m = s1.size(), n = s2.size(); vector<cp> A(m), B(n); for(int i=0;i<m;i++) A[i] = s1[i]-'a'+1; for(int i=0;i<n;i++) B[i] = s2[i]-'a'+1; reverse(A.begin(), A.end()); int len = 1; while(len < m + n) len <<= 1; A.resize(len, 0); B.resize(len, 0); fft(A, false); fft(B, false); for(int i=0;i<len;i++) A[i] *= B[i]; fft(A, true); for(int i=m-1;i<n;i++) cout << fixed << setprecision(0) << A[i].real() << " "; } ``` #### 3. 洛谷 FFT 教程 洛谷平台上有一些高质量的 FFT 教程,例如《浅谈 FFT——从 DFT 到 FFT》[^1]。这篇教程详细介绍了 FFT 的数学原理、DFT 的定义以及如何用 FFT 加速多项式乘法。此外,还提供了丰富的代码示例和实际应用场景。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值