1 条题解
-
0
自动搬运
来自洛谷,原作者为

基地A_I
现实和理想之间,不变的是跋涉,暗淡与辉煌之间,不变的是开拓。搬运于
2025-08-24 22:03:32,当前版本为作者最后更新于2020-01-21 14:43:38,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
Problem
经典题,好题。另外感谢 Rose_max 大佬的博客让我搞懂了这题。
Solution 1
对于一个排列 ,每一个 向 连一条无向边,构成一张由若干个简单环组成的无向图。目标状态即 个自环。
引理:将一个大小为 的简单环变成 个自环,至少需要 次交换。
利用数学归纳法,证明很简单,这里省略证明。
由此我们可以得到一个推论:假设图中有 个环,那么最小交换步数是 。
下面我们来考虑计数。
设 表示一个大小为 的环,在保证交换次数最少的情况下,有多少种方法将其变成目标状态。
每一次交换可以把大小为 的环拆成大小为 的两个环,。设 表示有多少种交换方法可以将一个大小为 的环拆成两个大小分别为 的环。可以发现:当 时,有一半的方法是重复的,;否则 。
将一个大小为 的环拆成大小为 的两个环后, 环需要 次交换达到目标状态,将这 次操作记为 个 ,同理将 环的 次操作记为 个 。由于 环与 环的操作互不干扰,两边的操作可以随意排列,因此这里就是一个多重集的全排列。
综上所述,可以得到 的状态转移方程:
$$f[i]=\sum_{x+y=i} f[x]*f[y]*T(x,y)*\frac{(i-2)!}{(x-1)!(y-1)!} $$(或许这个式子可以 一下,如果你可以将其 ,记得告诉我一下做法qwq)
假设排列 中有 个大小分别为 的环。因为这 个环的操作互不干扰,可以记其中第 个环的 次操作为 个 ,总操作就是一个多重集的全排列。答案是:
$$Ans=(\prod_{i=1}^k f[L_i])*(\frac{(n-k)!}{\prod_{i=1}^k (L_i-1)!}) $$在 意义下,上述除法可以用费马小定理求得逆元解决。复杂度带一个 。
时间复杂度:
求 数组和阶乘部分代码(预处理)
void Init() { fc[0] = 1; //阶乘 factorial for(int i=1;i<=1000;++i) fc[i] = fc[i-1] * i % mod; f[1] = 1; for(int i=2;i<=1000;++i) { for(int j=1;j<=i/2;++j) { // (x,y)和(y,x)只能算一次 int inv = Pow(fc[i-j-1]*fc[j-1]%mod, mod-2); f[i] = (f[i] + f[i-j]*f[j]%mod*T(i-j,j)%mod*fc[i-2]%mod*inv%mod) % mod; } } for(int i=1;i<=10;++i) printf("%lld\n",f[i]); }Solution 2
注意到上述算法并不能通过此题()。瓶颈在于求 数组这里。于是我们打表出 数组的前 项:
1 1 3 16 125 1296 16807 262144 4782969 100000000将其输入进 ,或者是对乘方敏感的同学可以发现:。有了这个规律,我们便可以解决这个问题了。
时间复杂度:
Code
Talk is cheap.Show me the code.
#include<bits/stdc++.h> #define int long long using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while(ch<'0' || ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<3)+(x<<1)+(ch^48); ch=getchar(); } return x * f; } const int N = 1e5+7, mod = 1e9+9; int n,cnt,ans; int p[N],f[N],fc[N],L[N]; bool vis[N]; int Pow(int x,int y) { int res = 1, base = x; while(y) { if(y&1) res = res*base%mod; base = base*base%mod; y >>= 1; } return res; } void Init2() { fc[0] = 1; for(int i=1;i<N;++i) fc[i] = fc[i-1] * i % mod; f[1] = 1; for(int i=2;i<N;++i) f[i] = Pow(i,i-2); } int Dfs(int u) { vis[u] = 1; if(vis[p[u]]) return 1; return Dfs(p[u]) + 1; } void work() { cnt = 0; memset(vis, 0, sizeof(vis)); n = read(); for(int i=1;i<=n;++i) p[i] = read(); for(int i=1;i<=n;++i) if(!vis[i]) L[++cnt] = Dfs(i); ans = fc[n-cnt] % mod; for(int i=1;i<=cnt;++i) { int inv = Pow(fc[L[i]-1], mod-2); ans = ans*f[L[i]]%mod*inv%mod; } printf("%lld\n",ans); } signed main() { Init2(); int T = read(); while(T--) work(); return 0; }Summary
模数看错自闭一中午,所以大家在任何地方都要细心呀!!这个题目让我学会两大经典模型:
-
将排列 变成递增序列的最小交换次数。
-
将排列 变成递增序列,保证最小交换次数下的方案数
另外让我学会一些计数题的 技巧/思路/套路。
-
- 1
信息
- ID
- 3774
- 时间
- 1000ms
- 内存
- 32MiB
- 难度
- 6
- 标签
- (无)
- 递交数
- 0
- 已通过
- 0
- 上传者