【洛谷】P8338 [AHOI2022] 排列

原题链接

题意

对于一个长度为 \(n\) 的排列 \(P = (p_1, p_2, \ldots, p_n)\) 和整数 \(k \ge 0\),定义 \(P\)\(k\) 次幂

\[P^{(k)} = \left( p^{(k)}_1, p^{(k)}_2, \ldots, p^{(k)}_n \right), \]

该排列的第 \(i\) 项为

\[p^{(k)}_i = \begin{cases} i, & k = 0, \\ p^{(k - 1)}_{p_i}, & k > 0. \end{cases} \]

容易证明任意排列的任意次幂都是一个排列。

定义排列 \(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;
}
posted @ 2023-03-15 14:52  曙诚  阅读(111)  评论(0)    收藏  举报