【洛谷】P8338 [AHOI2022] 排列
题意
对于一个长度为 \(n\) 的排列 \(P = (p_1, p_2, \ldots, p_n)\) 和整数 \(k \ge 0\),定义 \(P\) 的 \(k\) 次幂
该排列的第 \(i\) 项为
容易证明任意排列的任意次幂都是一个排列。
定义排列 \(P\) 的循环值 \(v(P)\) 为最小的正整数 \(k\) 使得 \(P^{(k + 1)} = P\)。
给出一个长度为 \(n\) 的排列 \(A = (a_1, a_2, \ldots, a_n)\),对于整数 \(1 \le i, j \le n\),定义 \(f(i, j)\):若存在 \(k \ge 0\) 使得 \(a^{(k)}_i = j\),则 \(f(i, j) = 0\),否则设排列 \(A_{i j}\) 为将排列 \(A\) 的第 \(i\) 项 \(a_i\) 和第 \(j\) 项 \(a_j\) 交换后得到的排列,则 \(f(i, j) = v(A_{i j})\)。
求 \(\sum_{i = 1}^{n} \sum_{j = 1}^{n} f(i, j)\) 的值。答案可能很大,你只需要输出其对 \(({10}^9 + 7)\) 取模的结果。
\(1 \le n \le 5 \times {10}^5\),\(1 \le a_i \le n\)
思路
连边 \(i \to p_i\),不难发现最终会形成若干个环,设这些环的大小为 \(a_1,a_2 \cdots a_m\),对于一个单独的环来说,要通过题意中的操作回到自己。显然需要的最多操作次数就为 \(a_i\)。那么对于所有环来说,需要的最小操作次数为 \(lcm(a_1,a_2,a_3\cdots a_m)\)。
而对于交换 \(p_i\) 和 \(p_j\) 的操作,实质上就是将 \(i\) 所在的环和 \(j\) 所在的环合并起来了,设它们原来所在的环分别为 \(x\) 和 \(y\),那么此时的答案就是 \(lcm(a_1,a_2\cdots a_{x-1},a_{x+1} \cdots a_{y-1},a_{y+1} \cdots a_m,a_x+a_y)\)。也就是将 \(a_x\) 和 \(a_y\) 删去,将 \(a_x+a_y\) 加入进来。
注意到环的个数最多有 \(O(n)\) 个,但是实质上不同大小的环最多只有 \(O(\sqrt{n})\) 个,而对于相同大小的环,本质上都是一样的,可以一起计算对答案的贡献。
先预处理出初始的 \(lcm\),用一个 \(multiset\) 维护每个素数对 \(lcm\) 的贡献,在计算贡献的时候只需要暴力修改即可,这一部分具体实现可以看代码。
code:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
using namespace std;
const int N=5e5+10,mod=1e9+7;
struct node{int pos,val;};
vector<node>d[N];//在d[i]这个数中,pos这个质数的最大出现次数,即对lcm的贡献为val
multiset<int>maxv[N];
int a[N],cnt[N],p[N],m,inv[N],n,prime[N],tot;bool vis[N];
void init()
{
inv[0]=inv[1]=1;for(int i=2;i<N;i++) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
for(int i=2;i<N;i++) if(!vis[i])
{
prime[++tot]=i;
for(int j=i;j<N;j+=i) vis[j]=true,d[j].push_back(node{i,i});
for(int j=i;j<N/i;j*=i) for(int k=j*i;k<N;k+=j*i) d[k].pop_back(),d[k].push_back(node{i,j*i});
}
}
void add(int w,int sign,int &res)
{
for(int i=0;i<d[w].size();i++)
{
res=1ll*res*inv[*maxv[d[w][i].pos].rbegin()]%mod;
if(sign==1) maxv[d[w][i].pos].insert(d[w][i].val);
else maxv[d[w][i].pos].erase(maxv[d[w][i].pos].find(d[w][i].val));
res=1ll*res*(*maxv[d[w][i].pos].rbegin())%mod;
}
}
void solve()
{
scanf("%d",&n);m=0;int lcm=1,ans=0;
for(int i=1;i<=n;i++) vis[i]=false,cnt[i]=0;
for(int i=1;i<=tot&&prime[i]<=n;i++) multiset<int>().swap(maxv[prime[i]]);
for(int i=1;i<=n;i++) scanf("%d",&p[i]);
for(int i=1;i<=n;i++) if(!vis[i])
{
int w=0;for(int j=i;!vis[j];j=p[j]) w++,vis[j]=true;cnt[w]++;
}
for(int i=1;i<=n;i++) if(cnt[i])
{
a[++m]=i;
for(int k=1;k<=cnt[i];k++) for(int j=0;j<d[i].size();j++) maxv[d[i][j].pos].insert(d[i][j].val);
}
for(int i=1;i<=tot&&prime[i]<=n;i++) maxv[prime[i]].insert(1),lcm=1ll*lcm*(*maxv[prime[i]].rbegin())%mod;
for(int i=1;i<=m;i++) for(int j=i;j<=m;j++)
{
if(i==j&&cnt[a[i]]==1) continue;
int res=lcm,val=(i==j)?1ll*cnt[a[i]]*(cnt[a[i]]-1)%mod:2ll*cnt[a[i]]*cnt[a[j]]%mod;//val表示选环的方案数
add(a[i]+a[j],1,res);add(a[i],-1,res);add(a[j],-1,res);
ans=(ans+1ll*a[i]*a[j]%mod*val%mod*res)%mod;//a[i]和a[j]是在环内选数的方案
add(a[i]+a[j],-1,res);add(a[i],1,res);add(a[j],1,res);
}
printf("%d\n",ans);
}
int main()
{
freopen("perm1.in","r",stdin);
init();int T;scanf("%d",&T);while(T--) solve();
return 0;
}