luogu P4778 Counting swaps

计数套路题?但是我连套路都不会,,,

拿到这道题我一脸蒙彼,,,感谢@poorpool 大佬的博客的指点

先将第\(i\)位上的数字\(p_i\)向\(i\)连无向边,然后构成了一个有若干环组成的无向图,可以知道某个点包含它的有且仅有一个环,因为所有点度数都为2(自环的点度数也是2吧qwq)

那么我们的最终目标就是把这个图转换成有\(n\)个自环的图,相当于把排列排好顺序

考虑对于一个\(m\)个点的环,把它变成\(m\)个自环至少需要\(m-1\)步(相当于排列\((2,3...m,1)\)变成\((1,2,3...m)\),这至少需要\(m-1\)次交换)

设把一个\(m\)个点的环变成\(m\)个自环的方案数为\(f_m\).因为一次操作可以把一个环拆成两个环,设拆出来两个环大小为\(x,y\),那么拆环的方案数\(g(x,y)\)为\[g(x,y)=\begin{cases}x,\quad (x=y)\\x+y, \quad others\end{cases} \]

就是对于每一个点,有两个点使得这一个点和那两个点中的一个进行交换操作可以分出大小为\(x,y\)的环,因为点对会被计算2次,所以方案为\(\frac{(x+y)*2}{2}=x+y\),如果\(x=y\),那么那两个点是同一点,所以方案要除以2

每个环之间的操作是不会相互影响的,所以可以交错进行操作,两个环贡献答案给一个大环时,两个环的操作交错所构成的排列数就是可重集的排列数(总元素个数阶乘除以每一中元素个数阶乘).综上,我们可以知道\[f_m=\sum_{i=1}^{\lfloor\frac{m}{2}\rfloor} g(i,m-i)f_if_{m-i}\frac{(m-2)!}{(i-1)!(m-i-1)!}\]

我们可以找出\(k\)个大小为\(a_1,a_2...a_k\)的环

把所有环的答案合并,同样每个环之间的操作是不会相互影响的,所以方案数要乘上一个可重集的排列数,可以得到\[ans=\prod_{i=1}^{k} f_{a_i}*\frac{(n-k)!}{\prod_{i=1}^{k}(a_i-1)!}\]

(注意边界,\(f_1=f_2=1\))

然而求\(f\)要\(O(n)\)的时间,总复杂度为\(O(nk)\)

考虑优化此算法,通过打表找规律可以发现\(f_i=i^{i-2}\)

然后求\(f\)只要要\(O(logn)\)了,爽!

然后就可以偷税的写代码了

// luogu-judger-enable-o2
#include<bits/stdc++.h>
#define LL long long
#define il inline
#define re register

using namespace std;
const LL mod=1000000009;
il LL rd()
{
    re LL x=0,w=1;re char ch;
    while(ch<'0'||ch>'9') {if(ch=='-') w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    return x*w;
}
int to[200010],nt[200010],hd[100010],tot=1;
int vis[200010],a[100010],tt;
LL jc[100010],cj[100010];   //不要问我为什么把阶乘数组的逆元写成cj(逃
bool v[100010];
il void add(int x,int y)
{
  ++tot;to[tot]=y;nt[tot]=hd[x];hd[x]=tot;
  ++tot;to[tot]=x;nt[tot]=hd[y];hd[y]=tot;
}
il void dfs(int x,int o,int h)
{
  if(!v[x]) ++a[o];v[x]=true;
  for(int i=hd[x];i;i=nt[i])
    {
      if(vis[i]==h) continue;
      vis[i]=vis[i^1]=h;
      dfs(to[i],o,h);
    }
}
il LL ksm(LL a,LL b)
{
  if(b<=0) return 1;    //i=1时,i-2会为-1 qwq
  LL an=1;
  while(b)
    {
      if(b&1) an=(an*a)%mod;
      a=(a*a)%mod;
      b>>=1;
    }
  return an;
}

int main()
{
  jc[0]=cj[0]=1;
  for(int i=1;i<=100000;i++) jc[i]=(jc[i-1]*i)%mod,cj[i]=ksm(jc[i],mod-2);
  int T=rd();
  for(int h=1;h<=T;h++)
  {
    tot=1;tt=0;
    memset(a,0,sizeof(a));
    memset(v,0,sizeof(v));
    memset(hd,0,sizeof(hd));
    int n=rd();
    for(int i=1;i<=n;i++) add(i,rd());
    for(int i=1;i<=n;i++)
      if(!v[i]) dfs(i,++tt,h);
    LL ans=jc[n-tt];
    for(int i=1;i<=tt;i++) ans=((ans*ksm(a[i],a[i]-2))%mod*cj[a[i]-1])%mod;
    printf("%lld\n",ans);
  }
  return 0;
}

原文地址:https://www.cnblogs.com/smyjr/p/9467183.html

时间: 2024-11-04 01:50:16

luogu P4778 Counting swaps的相关文章

P4778 Counting Swaps 题解

第一道 A 掉的严格意义上的组合计数题,特来纪念一发. 第一次真正接触到这种类型的题,给人感觉好像思维得很发散才行-- 对于一个排列 \(p_1,p_2,\dots,p_n\),对于每个 \(i\) 向 \(p_i\) 连一条边,可以发现整个构成了一个由若干环组成的图,目标是将这些环变为自环. 引理:把长度为 \(n\) 的环变为 \(n\) 个自环,最少交换次数为 \(n-1\). 用归纳法证,对于当前情况,任意一次交换都将其拆为两个环,由淘汰赛法则可知引理成立. 记 \(F_n\) 表示在最

counting swaps

3602 Counting Swaps 0x30「数学知识」例题 背景 https://ipsc.ksp.sk/2016/real/problems/c.html Just like yesterday (in problem U of the practice session), Bob is busy, so Alice keeps on playing some single-player games and puzzles. In her newest puzzle she has a

lfyzoj104 Counting Swaps

问题描述 给定你一个 \(1 \sim n\) 的排列 \(\{p_i\}\),可进行若干次操作,每次选择两个整数 \(x,y\),交换 \(p_x,p_y\). 请你告诉穰子,用最少的操作次数将给定排列变成单调上升的序列 \(1,2,\ldots,n\),有多少种方式呢?请输出方式数对 \(10^9+9\) 取模的结果. 输入格式 第一行一个整数 \(T\) 代表数据组数. 每一组测试数据,第一行是一个整数 \(n\) 代表排列中的元素个数,第二行 \(n\) 个整数,是这个排列. 输入数据中

luogu P2992 [USACO10OPEN]三角形计数Triangle Counting

https://www.luogu.org/problemnew/solution/P2992 考虑包含原点,不包含原点的三角形有什么特征. 包含原点的三角形:任意找一个顶点和原点连线,一定能把另外两个顶点隔开到两侧. 不包含原点的:三个顶点中只有一个顶点满足:和原点连线后,能把另外两个顶点隔开到两侧. 因此我们统计这样的三点组(x,y,z)的数目:x和原点的连线能把y和z隔开在两侧. 一共C(n,3)个三角形,包含原点的贡献3个三点组,不包含的只贡献1个. 统计三点组的数目只需要把所有点按照极

[线段树合并] Luogu P3605 [USACO17JAN]Promotion Counting晋升者计数

给一棵 N 个点的树,每个点有一个权值,求每个点的子树中有多少个点的权值比它大. 考虑线段树合并,将权值离散化,每个点开一棵权值线段树. 求答案时直接在权值线段树上查询,线段树合并时类似于可并堆. 要注意的是线段树要动态开点,合并时别忘了 up. 内存什么的最好算一下,数组别开小了. 1 #include<bits/stdc++.h> 2 #define rep(i,a,b) for(register int i=a;i<=b;++i) 3 #define rpd(i,a,b) for(

luogu P3799 妖梦拼木棒

二次联通门 : luogu P3799 妖梦拼木棒 /* luogu P3799 妖梦拼木棒 用一个桶存下所有的木棒 美剧两根短的木棒长度 后随便乘一乘就 好了.. */ #include <algorithm> #include <cstdio> #define Mod 1000000007 #define Max 5000 void read (int &now) { now = 0; register char word = getchar (); while (wo

[luogu P1967][NOIp2013]P1967 货车运输

题目描述 A 国有 n 座城市,编号从 1 到 n,城市之间有 m 条双向道路.每一条道路对车辆都有重量限制,简称限重.现在有 q 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物. 输入输出格式 输入格式: 输入文件名为 truck.in. 输入文件第一行有两个用一个空格隔开的整数 n,m,表示 A 国有 n 座城市和 m 条道 路. 接下来 m 行每行 3 个整数 x. y. z,每两个整数之间用一个空格隔开,表示从 x 号城市到 y 号城市有一条限重为 z

UVA - 12075 Counting Triangles

Description Triangles are polygons with three sides and strictly positive area. Lattice triangles are the triangles all whose vertexes have integer coordinates. In this problem you have to find the number of lattice triangles in anMxN grid. For examp

POJ 2386 Lake Counting 搜索题解

简单的深度搜索就可以了,看见有人说什么使用并查集,那简直是大算法小用了. 因为可以深搜而不用回溯,故此效率就是O(N*M)了. 技巧就是增加一个标志P,每次搜索到池塘,即有W字母,那么就认为搜索到一个池塘了,P值为真. 搜索过的池塘不要重复搜索,故此,每次走过的池塘都改成其他字母,如'@',或者'#',随便一个都可以. 然后8个方向搜索. #include <stdio.h> #include <vector> #include <string.h> #include