[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]}}\) 是经典的分组方案;
- 中间的 \(\frac{1}{(\bold k[j])!}\) 是去除同样大小的组的顺序;
- 后面的一堆是每组的贡献;
简化一下得到:
注意 \(j\) 的取值是有限制的(也就是合法的 \(m\) 取值),我们拿这些值来尝试做多重背包,就会......TLE......
😅,这里第一次推又忘记除掉 \(\frac{1}{(\bold k[j])!}\) 了
我们需要优化计算过程。仔细研究后面的这堆东西的形式,我们发现......有点眼熟?
什么东西既可以分组,又可以顺便去除同等大小的组的顺序?
......这不是 \(\exp\) 嘛。
研究一下是拿什么在做 \(\exp\),其实不难看出,设:
那么就有 \(G(x)=e^{F(x)}\),且此时的答案就是 \(n!l^n[x^n]G(x)\)!
唔,一般来说我们可能是将某个 DP 改写写成 \(\exp\) 形式方便多项式运算,但是这里我们只需要求 \([x^n]G(x)\),而且模数也不方便 NTT,所以,我们可以转化回 DP 本身的形式来计算。
做一些简单的变换可以得到:
于是得到转移为:
其中直接记 \([x^n]G(x)=g_n\)。
这样计算,时间复杂度就是 \(O(n\sigma_0(K))\),足以通过本题。
小结:
-
前面的分析步骤,自己推理虽已略有雏形,但是细节还不到位!
-
推理计数的式子的时候,也有细节遗漏;
-
最后一步将 \(\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;
}