[Gym101879K] Portuguese Pastimes

题目

点这里看题目。

极度简洁版本:

给定长度为 \(n\) 的置换 \(A\),在对称群 \(S_n\) 中求 \(P^k=A\) 的解的个数。

数据范围:对于 \(100\%\) 的数据,有 \(1\le n\le 10^5,0\le k\le 10^6\)

分析

绝世好阴间题。

首先,通过阅读样例 2 不难发现如下事实:

\(P\) 中的轮换一定由若干个 \(A\) 中的轮换拼凑而成

那么不妨研究一下如何拼凑轮换才可满足条件。

假设现在有长度为 \(l_1,l_2,l_3,\dots,l_m\)\(m\) 个轮换,第 \(j\) 个轮换中的元素依次为 \(a_{j,0},a_{j,1},a_{j,2},\dots,a_{j,l_j-1}\),那么最终这个轮换的结果是被旋转 \(q=K\bmod\left(\sum_jl_j\right)\) 次。为了方便,下面记 \(L=\sum_jl_j\)

假如提出 \(a_1\) 这个轮换来考虑,那么新轮换上 \(a_{1,0}\) 所在的位置看作 0,于是 \(a_{1,1}\) 位置就是 \(q\)\(a_{1,2}\) 位置就是 \(2q\bmod L\)。以此类推,\(a_{1,k}\) 位置为 \(kq\bmod L\)。注意这个过程的隐含条件

  • \(l_1q\bmod L=0\)
  • \(\not\exists k\in (0,l_1),kq\bmod L=0\)

第一个条件告诉我们,存在 \(r_1\) 使得 \(l_1q=r_1L\),这个时候有 \(l_1=\frac{r_1L}{q}\);另一方面,第二个条件告诉我们 \(l_1=\frac{L}{\gcd(q,L)}\)。注意到这个结果中并不包含与轮换本身相关的信息,因此可以得到 \(l_1=l_2=\dots=l_m=l\)

这告诉我们 \(L=ml\),因此有 \(L|lq\Rightarrow m|q\)。考虑到 \(l\) 的表达式,可以得到 \(\gcd(q,L)=m\),稍微变一下形得到 \(\gcd(\frac{K}{m},l)=1\)

这里对于 \(m\) 的限制就是:

  • \(m|K\)
  • \(\gcd(\frac{K}{m},l)=1\)

😅,这里其实我最初推错了......误认为 \(m=q\),所以光速看出 \(l\) 相等,但是没有意识到 \(m|q\) 这个条件,而以为 \(m=q\)......​

还是要细心,中间的逻辑要严谨一点:比如 \(l_1q\bmod L=0\not\Leftrightarrow l_1q=L\)......


上面得到的条件是必要的,但是不难看出只要对于每个轮换都有这样的条件满足,那么也是充分的。

所以现在我们就可以用这些条件来计数了,高不高兴?

假设当前的 \(m\) 个轮换已经被确定,但是内部顺序还没有确定,那么我们可以枚举一个内部顺序 \((m-1)!\)(注意环排列需要去重),对于每一种顺序下,我们需要确定除了第一个轮换之外(还是注意去重)的每个轮换的“首个”元素,也即是 \(l^{m-1}\)

现在可以考虑分组的问题了:假如总共有 \(p\) 个长度为 \(l\) 的轮换,某种划分方案以 \(\bold{k}\) 来表示,其中 \(\bold{k}[j]\) 表示包含 \(j\) 个轮换的组的数量,那么可以得到贡献为:

\[\frac{n!}{\prod_{j=1}^n(j!)^{\bold{k}[j]}}\times \prod_{j=1}^n\frac{1}{(\bold{k}[j])!}\times ((j-1)!l^{j-1})^{\bold{k}[j]} \]

为了避免阅读障碍,这里简单解释一下:

  1. 前面的 \(\frac{n!}{\prod_{j=1}^n(j!)^{\bold k[j]}}\) 是经典的分组方案;
  2. 中间的 \(\frac{1}{(\bold k[j])!}\) 是去除同样大小的组的顺序;
  3. 后面的一堆是每组的贡献;

简化一下得到:

\[n!l^n\prod_{j=1}^n\frac{1}{(\bold k[j])!}\times(jl)^{-\bold k[j]} \]

注意 \(j\) 的取值是有限制的(也就是合法的 \(m\) 取值),我们拿这些值来尝试做多重背包,就会......TLE......

😅,这里第一次推又忘记除掉 \(\frac{1}{(\bold k[j])!}\) 了​


我们需要优化计算过程。仔细研究后面的这堆东西的形式,我们发现......有点眼熟?

什么东西既可以分组,又可以顺便去除同等大小的组的顺序?

......这不是 \(\exp\) 嘛。

研究一下是拿什么在做 \(\exp\),其实不难看出,设:

\[G(x)=\sum_{n\ge 0}\sum_{\bold {k}}[\textstyle{\sum_j \bold k[j]j=n}]\displaystyle{\prod_{j=1}^n\frac{x^{\bold k[j]j}}{(\bold k[j])!}}\times (jl)^{-\bold k[j]}\\ F(x)=\sum_{j=1}^n[j|K][\gcd(\frac K j,l)=1]\frac{x^j}{jl} \]

那么就有 \(G(x)=e^{F(x)}\),且此时的答案就是 \(n!l^n[x^n]G(x)\)

唔,一般来说我们可能是将某个 DP 改写写成 \(\exp\) 形式方便多项式运算,但是这里我们只需要求 \([x^n]G(x)\)而且模数也不方便 NTT,所以,我们可以转化回 DP 本身的形式来计算。

做一些简单的变换可以得到:

\[\begin{aligned} G(x) &=e^{F(x)}\\ \ln G(x) &=F(x)\\ \frac{G'(x)}{G(x)} &=F'(x)\\ G(x) &=\int F'(x)G(x)+1 \end{aligned} \]

于是得到转移为:

\[g_n=[n=0]+\frac{1}{nl}\sum_{j=1}^n[j|K][\gcd(\frac{K}{j},l)=1]g_{n-j} \]

其中直接记 \([x^n]G(x)=g_n\)

这样计算,时间复杂度就是 \(O(n\sigma_0(K))\),足以通过本题。

小结:

  1. 前面的分析步骤,自己推理虽已略有雏形,但是细节还不到位!

  2. 推理计数的式子的时候,也有细节遗漏;

  3. 最后一步\(\exp\) 转化为 DP 计算,比较巧妙,甚至有一点逆向思维的感觉,

    当然,不应该被 DP->GF 的思路套死了,反过来也应该是成立的,且是有价值的思考方向。

代码

#include <bits/stdc++.h>
using namespace std;

#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )

typedef long long LL;

const int mod = 1e9 + 7;
const int MAXN = 1e6 + 5;

template<typename _T>
void read( _T &x )
{
    x = 0; char s = getchar(); int f = 1;
    while( s < '0' || '9' < s ) { f = 1; if( s == '-' ) f = -1; s = getchar(); }
    while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
    x *= f;
}

template<typename _T>
void write( _T x )
{
    if( x < 0 ) putchar( '-' ), x = -x;
    if( 9 < x ) write( x / 10 );
    putchar( x % 10 + '0' );
}

template<typename _T>
inline _T MAX( const _T a, const _T b )
{
	return a > b ? a : b;
}

template<typename _T>
inline _T MIN( const _T a, const _T b )
{
	return a < b ? a : b;
}

template<typename _T>
inline _T ABS( const _T a )
{
	return a < 0 ? -a : a;
}

int dp[MAXN];

int buc[MAXN];
int avai[MAXN], len;
int tmp[MAXN], coe[MAXN], tot;

int fac[MAXN], inv[MAXN], ifac[MAXN];
int P[MAXN];
int N, K;

bool vis[MAXN];

inline int Qkpow( int, int );
inline int Mul( int x, int v ) { return 1ll * x * v % mod; }
inline int Inv( const int a ) { return Qkpow( a, mod - 2 ); }
inline int Sub( int x, int v ) { return ( x -= v ) < 0 ? x + mod : x; }
inline int Add( int x, int v ) { return ( x += v ) >= mod ? x - mod : x; }
inline int Gcd( int a, int b ) { for( int z ; b ; z = a, a = b, b = z % b ) ; return a; }
inline int C( int n, int m ) { return n < m ? 0 : Mul( fac[n], Mul( ifac[m], ifac[n - m] ) ); }

inline int Qkpow( int base, int indx )
{
	int ret = 1;
	while( indx )
	{
		if( indx & 1 ) ret = Mul( ret, base );
		base = Mul( base, base ), indx >>= 1;
	}
	return ret;
}

void Init( const int n )
{
	inv[1] = 1; 
	rep( i, 2, n ) inv[i] = Mul( inv[mod % i], mod - mod / i );
	fac[0] = 1; rep( i, 1, n ) fac[i] = Mul( fac[i - 1], i );
	ifac[n] = Inv( fac[n] ); per( i, n - 1, 0 ) ifac[i] = Mul( ifac[i + 1], i + 1 );
}

int main()
{
	read( N ), read( K );
	rep( i, 1, N ) read( P[i] );
	Init( MAX( N, K ) );
	rep( i, 1, N ) if( ! vis[i] )
	{
		int siz = 0;
		for( int u = i ; ! vis[u] ; u = P[u] )
			vis[u] = true, siz ++;
		buc[siz] ++;
	}
	for( int i = 1 ; i <= K ; i ++ )
		if( ! ( K % i ) ) avai[++ len] = i;
	int ans = 1, n;
	rep( l, 1, N ) if( buc[l] )
	{
		n = buc[l], tot = 0;
		rep( i, 0, n ) dp[i] = 0; dp[0] = 1;
		rep( i, 1, len ) 
			if( Gcd( K / avai[i], l ) == 1 )
				tot ++, tmp[tot] = avai[i], coe[tot] = Inv( l );
		rep( i, 1, n )
		{
			for( int j = 1 ; j <= tot && tmp[j] <= i ; j ++ )
				dp[i] = Add( dp[i], dp[i - tmp[j]] );
			dp[i] = Mul( dp[i], Mul( inv[i], inv[l] ) ); 
		}
		ans = Mul( ans, Mul( Mul( Qkpow( l, n ), fac[n] ), dp[n] ) );
	}
	write( ans ), putchar( '\n' );
	return 0;
}
posted @ 2021-07-05 21:43  crashed  阅读(79)  评论(0编辑  收藏  举报