【HDU5896 2016 ACM ICPC Asia Regional Shenyang Online E】【DP 排列组合 分治ntt】Running King n个点构成含环无向图的方案数.cp

Running King

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 201    Accepted Submission(s): 71


Problem Description
In People's Republic Of Running, people are crazy about runnning. They keep a habit of running everyday, especially the Head King, because he isn't confident about his figure (please don't tell him~).

The Head King is going to build a new city, which contains   N  towns. He want to build some bidirectional roads between some of the towns (each road connect two towns) and choose some of them to form his private running route. The Head King   can't accept more than one bidirectional roads between two towns, because he has no sense of direction and it will make him bemused. He prefers enjoying the landscape changing while running, so he   can't stand any same road on his running route.  

Now, the Head King wants to know the total number of scheme of road constructing. Every scheme satisfies that there exist a town for him both to start and finish running(the running route contains at least one road). Please note the   towns in the city needn't to be connected to each other.
 

Input
First line contains   T(T10) , the number of test cases.

For each case, there is one single line containing one non-negative integer   N(n2e5) .

It is guarantee that there are at most one case in which   n1000
 

Output
For each test case, output a single number —— the number of schemes modulo the population of King's country( 1004535809 ).
 

Sample Input
  
  
3 2 3 4
 

Sample Output
  
  
0 1 26
Hint
In sample 1, the Head King can't design any scheme. In sample 2, the Head King can build three roads:1-2, 2-3, 3-1, and he can start from town 1 (2 and 3 are also legal) and run along the route:1-2-3-1 (or 1-3-2-1). In sample 3, one possible scheme is building 3 roads:1-3, 3-4, 4-1, and the Head King can start from town 3 and run along the route:3-1-4-3.
 

Source

#include<stdio.h> 
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); }
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b>a)a = b; }
template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b<a)a = b; }
const int N = 20020, M = 0, Z = 1004535809, inf = 0x3f3f3f3f;
template <class T1, class T2>inline void gadd(T1 &a, T2 b) { a = (a + b) % Z; }
int casenum, casei;
int n;
LL f[N];
LL g[N];
LL c[2][N];
LL qpow(LL x, int p)
{
	LL y = 1;
	while (p)
	{
		if (p & 1)y = y*x%Z;
		x = x*x%Z;
		p >>= 1;
	}
	return y;
}
LL fac[N];
LL inv[N];
void factorial()
{
	int top = 2e4;
	fac[0] = 1; for (int i = 1; i <= top; i++)fac[i] = fac[i - 1] * i%Z;
	inv[0] = inv[1] = 1; for (int i = 2; i <= top; i++)inv[i] = inv[Z%i] * (Z - Z / i) % Z;
	for (int i = 2; i <= top; i++)inv[i] = inv[i] * inv[i - 1] % Z;
}
const int G = 3;			//G为原根,模数不同原根不同。
const int P = 1004535809;	
int X[1 << 16];
int Y[1 << 16];
int wn[25];
int qpow(int x, int k, int p)
{
	int ret = 1;
	while (k)
	{
		if (k & 1) ret = 1LL * ret * x % p;
		k >>= 1;
		x = 1LL * x * x % p;
	}
	return ret;
}
void getwn()
{
	for (int i = 1; i <= 21; ++i)
	{
		int t = 1 << i;
		wn[i] = qpow(G, (P - 1) / t, P);
	}
}
void NTT(int y[], int len, int rev)
{
	for (int i = 1, j, k, t; i < len; ++i)
	{
		for (j = 0, k = len >> 1, t = i; k; k >>= 1, t >>= 1) j = j << 1 | t & 1;
		if (i < j) swap(y[i], y[j]);
	}
	int id = 0;
	for (int s = 2, ds = 1; s <= len; ds = s, s <<= 1)
	{
		++id;
		for (int k = 0; k < len; k += s)
		{
			LL w = 1;
			for (int i = k; i < k + ds; ++i)
			{
				int t = w * y[i + ds] % P;
				y[i + ds] = y[i]; gadd(y[i + ds], P - t);
				gadd(y[i], t);
				w = w * wn[id] % P;
			}
		}
	}
	if (rev == -1)
	{
		for (int i = 1; i < len / 2; ++i) swap(y[i], y[len - i]);
		LL inv = qpow(len, P - 2, P);
		for (int i = 0; i < len; ++i)y[i] = y[i] * inv % P;
	}
}
/*
                          f[j]         g[i-j]
	 = fac(1~i-1) * ∑( ---------- * ------------  )
	                    fac[j-1]      fac[i-j]

	两个式子乘起来,变成了
	f[l]*a[1](弃)
	f[l]*a[2] + f[l+1]*a[1](弃)
	f[mid]*a[1] + f[mid-1]*a[2] + ... f[l] * a[mid+1-l],这个算作贡献,加入f[mid+1]
	继续做下去,我们可以收集完f[mid+1]~f[r]的贡献

	这道题也一样,只是f[x]->g[x]/fac[x]
	a[x]->f[x]/fac[x-1]
*/
void cdq(int l, int r)
{
	if (l == r)
	{
		g[l] = (g[l] * fac[l - 1] + f[l]) % Z;
		return;
	}
	int mid = (l + r) >> 1;
	cdq(l, mid);
	int len1 = mid - l + 1;
	int len2 = r - l + 1;
	int len = 1; while (len < len1 + len2)len <<= 1;
	for (int i = 0; i < len1; ++i)X[i] = g[l + i] * inv[l + i] % Z;
	for (int i = len1; i < len; ++i)X[i] = 0;
	for (int i = 0; i < len2; ++i)Y[i] = f[i + 1] * inv[i] % Z;
	for (int i = len2; i < len; ++i)Y[i] = 0;
	NTT(X, len, 1);
	NTT(Y, len, 1);
	for (int i = 0; i < len; ++i)Y[i] = (LL)X[i] * Y[i] % Z;
	NTT(Y, len, -1);
	int dif = (mid + 1) - (len1 - 1);
	for (int i = mid + 1; i <= r; ++i)gadd(g[i], Y[i - dif]);
	cdq(mid + 1, r);
}
void init()
{
	f[1] = 1; for (int i = 2; i <= 20000; ++i)f[i] = qpow(i, i - 2);
	g[0] = 1;
	cdq(1, 20000);
}
int main()
{
	//brute_force();
	getwn();
	factorial();
	init();
	scanf("%d", &casenum);
	for (casei = 1; casei <= casenum; ++casei)
	{
		scanf("%d", &n);
		int m = n*(n - 1) / 2;
		int tot = qpow(2, m);
		printf("%d\n", (tot + Z - g[n]) % Z);
	}
	return 0;
}
/*
【trick&&吐槽】
杭电也能1e8,暴力还是出奇迹

【题意】
有n个点,我们在其中可以建立m条边。
这m条边怎么连接是无所谓的,但是不能形成重边和自环。
让你在这种情况下,输出有多少种连边方式,使得连成的图中至少存在一个环

【类型】
分治ntt

【分析】
什么情况下没有环?全是森林。
一共有多少种连边方法?2^c(n,2)
如果我们能求出全是森林的方案数,
便知道了没有环的方案数,也就得到了答案。

我们要想着如何去递推——
最后形成的是森林,其实就相当于本来有一片i个点的森林,我们现在加入了一棵j个点的树,形成i+j个点的森林。
定义f[i]表示i个点形成的森林的方案数
定义g[i]表示i个点形成的树的方案数

根据cayley公式得到,g[n]=n^(n-2)
为什么这样子呢?我们可以通过Prüfer编码证明——
Prüfer编码是对带标号无根树的编码方式:
给定一棵带标号的无根数,找出编号最小的叶子节点,写下其相邻节点的编号,并删掉这个叶子节点。
反复执行这个过程,直到最后只剩下2个节点为止。
由于节点数>2的树总存在叶子节点,因此一棵n个节点的无根树,唯一地对应了一个长度为n-2的数列
数列中的每个数都在1~n的范围内,因此只需要说明,
任何一个长为n-2、取值范围在1到n之间的数列都唯一地对应了一棵n个节点的无根树,
这样我们的带标号无根树就和Prüfer编码之间形成一一对应的关系,Cayley公式便不证自明了
这个反构造即可得证。
http://www.matrix67.com/blog/archives/682

(plus:N个节点能够构成的不同形状的二叉树的种类为C(2n,n)/(n+1))

而g[]的公式是怎样呢?
我们假设已经求得了g[0]~g[n-1]
其中g[i]表示i个点构成的森林的数量,然后我们要考虑再加入一个点。
为了考虑入排列组合顺序,我们不妨使得这最后加入的点,为当前所有点中最大的一个。

★只需要有一个节点标号,就能够使得顺序问题得到解决。
那么便有g[i]=∑(g[i-j] * c(i-1,j-1) * f[j]);
数据范围很小,我们可以大数据打表,小数据暴力,是可以AC的。

【时间复杂度&&优化】
O(n^2)

当然,这道题的标准做法不是暴力。我们研究下面这个式子:
g[i]=∑(g[i-j] * c(i-1,j-1) * f[j]);
这种式子是标准的fft式型。

我们把该式子拆开——
g[i] =  g[i-j] * fac(1~i-1) * f[j]
       ∑--------------------------
	     fac(1~j-1)*fac(1~i-j)

                          f[j]         g[i-j]
	 = fac(1~i-1) * ∑( ---------- * ------------  )
	                    fac[j-1]      fac[i-j]

第一个做的分治fft,式子是这样的:

f[i]=∑(a[j] * f[i-j])
显然是一样的,可以利用cdq分治求解——
回顾f[i]=∑(a[j] * f[i-j])的解法
所谓cdq分治,利用了这道题目中的——想求f[i],需要加进所有f[j,j<i]对f[i]的贡献
我们把区间分成了以下两块[l,mid] [mid+1,r],
我们先处理[l,mid],
然后把[l,mid]的影响向[mid+1,r]转移,
再去处理[mid+1,r]。

我们先写出f[l], f[l+1], f[l+2], f[l+3], ..., f[mid]
然后写出a[1], a[2], a[3], ..., a[]

两个式子乘起来,变成了
f[l]*a[1](弃)
f[l]*a[2] + f[l+1]*a[1](弃)
f[mid]*a[1] + f[mid-1]*a[2] + ... f[l] * a[mid+1-l],这个算作贡献,加入f[mid+1]
继续做下去,我们可以收集完f[mid+1]~f[r]的贡献

这道题也一样,只是f[x]->g[x]/fac[x]
                a[x]->f[x]/fac[x-1]
*/


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值