首页 > 其他分享 >题解 P8338 [AHOI2022] 排列

题解 P8338 [AHOI2022] 排列

时间:2023-07-17 21:48:19浏览次数:36  
标签:res int 题解 ll P8338 cir num AHOI2022 mod

恶心题。

每次操作,相当与把第 \(i\) 个数置换到 \(p_i\),于是可以连边。

因为 \(i\) 和 \(p_i\) 互不相同,所以对于每一个点,有且仅有一条出边和一条入边,即若干个简单环。

那么最少操作 \(\operatorname{lcm}(a_1,a_2,a_3...a_{x-2},a_{x-1},a_x)\) 次点会都回到原位。其中 \(a_i\) 表示第 \(i\) 个环的大小。

交换两个点 \(i,j\) 相当于把 \(i\) 所在的环和 \(j\) 所在的环合并。

我们只关注环的数量和大小,所以可以将相同大小的环合并。

因为点数之和为n,所以不同大小的环最多有 \(\sqrt{n}\) 个。

每次计算 \(lcm\) 时,直接暴力删数加数即可。

实现时只需要记录每个质因子所出现的最大、次大和次次大的指数,因为每次只合并两个环。

复杂度 \(O(n\log n)\)。

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+5,mod=1e9+7;
int T,n,x[N],cnt[N],cir[N],fa[N],b[N][3],vis[N][3],vis2[N],p[N];
struct node{
  int val,num;
  node(int val=0,int num=0):val(val),num(num){}
};
vector<node>y[N];
vector<int>type,tmp;
int ff(int u){return fa[u]==u?u:fa[u]=ff(fa[u]);}
ll ksm(ll xx,ll yy){
  ll res=1;
  while(yy){
    if(yy&1)res=res*xx%mod;
    yy>>=1;
    xx=xx*xx%mod;
  }
  return res;
}
int main(){
  ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
  cin>>T;
  int tot=0;
  for(ll i=2;i<=5e5;++i){
    if(!x[i])p[++tot]=i,x[i]=i;
    for(int j=1;j<=tot&&p[j]*i<=5e5;++j){
      x[p[j]*i]=p[j];
      if(i%p[j]==0)break;
    }
  }
  for(int i=2;i<=5e5;++i){
    int t=i;
    while(x[t]){
      if(y[i].size()&&y[i].back().val==x[t])++y[i][y[i].size()-1].num;
      else y[i].push_back(node(x[t],1));
      t/=x[t];
    }
  }
  while(T--){
    cin>>n;
    type.clear();
    for(int i=1;i<=n;++i)fa[i]=i,cnt[i]=cir[i]=b[i][0]=b[i][1]=b[i][2]=0;
    for(int i=1;i<=n;++i){
      int a;cin>>a;
      fa[ff(i)]=ff(a);
    }
    tmp.clear();
    for(int i=1;i<=n;++i)++cnt[ff(i)];
    for(int i=1;i<=n;++i)if(cnt[i])++cir[cnt[i]],tmp.push_back(cnt[i]);
    for(int i=1;i<=n;++i)if(cir[i])type.push_back(i);
    for(int i=0;i<tmp.size();++i){
      int v=tmp[i];
      for(int j=0;j<y[v].size();++j){
        int w=y[v][j].val,t=y[v][j].num;
        if(t>=b[w][0])b[w][2]=b[w][1],b[w][1]=b[w][0],b[w][0]=t;
        else if(t>=b[w][1])b[w][2]=b[w][1],b[w][1]=t;
        else b[w][2]=max(b[w][2],t);
      }
    }
    ll z=1,res=0;
    for(int i=2;i<=n;++i)z=z*ksm(i,b[i][0])%mod;
    for(int i=0;i<type.size();++i){
      for(int j=i;j<type.size();++j){
        int t=type[i],t2=type[j];ll ans=z;
        tmp.clear();
        for(int l=0;l<y[t].size();++l){
          int w=y[t][l].val,num=y[t][l].num;
          if(!vis2[w])tmp.push_back(w),vis2[w]=1;
          if(b[w][0]==num&&!vis[w][0])vis[w][0]=1;
          else if(b[w][1]==num)vis[w][1]=1;
        }
        for(int l=0;l<y[t2].size();++l){
          int w=y[t2][l].val,num=y[t2][l].num;
          if(!vis2[w])tmp.push_back(w),vis2[w]=1;
          if(b[w][0]==num&&!vis[w][0])vis[w][0]=1;
          else if(b[w][1]==num)vis[w][1]=1;
        }
        for(int l=0;l<tmp.size();++l){
          int w=tmp[l];
          for(int k=0;k<2&&vis[w][k]==1;++k){
            ans=ans*ksm(ksm(w,b[w][k]-b[w][k+1]),mod-2)%mod;
          }
        }
        ll tt=t+t2;
        if(tt<=n){
          for(int l=0;l<y[tt].size();++l){
            int w=y[tt][l].val,num=y[tt][l].num,k=0;
            while(vis[w][k]==1)++k;
            if(num>b[w][k])ans=ans*ksm(w,num-b[w][k])%mod;
          }
        }
        if(t==t2)res=(res+(ll)cir[t]*t%mod*(cir[t]-1)%mod*t%mod*ans%mod)%mod;
        else res=(res+(ll)2*cir[t]*t%mod*cir[t2]%mod*t2%mod*ans%mod)%mod;
        for(int l=0;l<tmp.size();++l){
          int w=tmp[l];
          vis2[w]=vis[w][0]=vis[w][1]=0;
        }
      }
    }
    cout<<res<<endl;
  }
  return 0;
}

标签:res,int,题解,ll,P8338,cir,num,AHOI2022,mod
From: https://www.cnblogs.com/HQJ2007/p/17561320.html

相关文章

  • 题解 CF840C On the Bench
    这是一篇简洁易懂的良心题解,提供了多种做法。对于两个数\(x,y\),定义\(x=n^2\cdottx,y=m^2\cdotty\)。如果\(x\cdoty\)为平方数,则说明\(tx=ty\)。所以我们可以将每个数除去其平方因子,比较相邻两数是否相等即可。F1:定义\(f_{i,j,k}\)为插入\(i\)个数、有\(j\)对......
  • 题解 P3803 【模板】多项式乘法(FFT)
    感觉题解区不是写的太高深,就是写的太高深。所以给初中、小学和幼儿园的萌新准备一篇简单易懂的良心题解~前置知识一、多项式的系数表示法和点值表示法。\(A(x)=\sum\limits_{i=0}^{n-1}a_i\cdotx^i\)系数:\((a_0,a_1,a_2...a_{n-2},a_{n-1})\)。点值:\((x_0,y_0),(x_1,y_1)...(......
  • 题解 CF41D
    基础DP题。定义\(f_{i,j,k}\)表示从第一行走到\((i,j)\),且数字总和模\(p\)等于\(k\)。转移方程为:\[f_{i+1,j-1,(k+a_{i+1,j-1})\bmodp}=\max(f_{i,j,k}+a_{i+1,j-1})\]\[f_{i+1,j+1,(k+a_{i+1,j+1})\bmodp}=\max(f_{i,j,k}+a_{i+1,j+1})\]同时还需要定义\(g_{i,j......
  • 题解 P6091 【模板】原根
    题解太高深,自己整理一份。阶的定义:若\(\gcd(a,m)=1\),则使\(a^l\equiv1\pmod{m}\)的最小的\(l\)称为\(a\)关于模\(m\)的阶,记为\(\operatorname{ord}_ma\)。阶的性质:根据欧拉定理,\(a^{\varphi(m)}\equiv1\pmod{m}\),所以\(\operatorname{ord}_ma\mid\varphi(m)\)......
  • 题解 CF417D
    \(m\le20\),状压DP。首先可以根据每个人的\(k\)从小到大排序。定义\(f_{i,j}\)表示考虑到第\(i\)个人,完成了\(j\)状态的题目,不考虑显示器所需的最小价格。转移显然为\(f_{i,j|s_i}=\min(f_{i-1,j}+x_i)\)。最终答案为\(ans=\min\limits_{i=1}^{n}f_{i,S}+b\cdotk_......
  • 题解 CF985E
    贪心+DP。先从小到大排序。定义\(f_i\)表示序列\([1,i]\)能否分组。转移为\(f_i|=f_j[a_i-a_j\led,j\lei-k+1]\)。右区间可以直接算出来,左区间可以二分或根据单调性弄个指针。定义\(sum_i=\sum\limits_{t=1}^{i}f_t\),前缀和减一下判断是否为正即可。复杂度\(O(n\lo......
  • 题解 P4815 [CCO2014] 狼人游戏
    看题目限制,可以发现如果将机器人作为点,指控和保护关系作为边,可以建出一个森林,就下来就是传统的树形背包了。设\(f_{i,j,0/1}\)表示当前点为\(i\),子树内有\(j\)个狼人,当前点是否为狼人的方案数。初始化:\(f_{u,0,0}=f_{u,1,1}=1\)当前点为狼:指控:\(f_{u,j,1}=f_{u,j-k,......
  • 题解 [ABC276F] Double Chance
    很容易想到分类。我们可以把\(1\)到\(i-1\)的球分为两类,一类是权值小于\(val_i\),另一类是权值大于\(val_i\)。对于第一类,\(sum\)加上小于\(val_i\)的球的个数乘以\(val_i\)。对于第二类,\(sum\)加上所有大于\(val_i\)的球的权值。这显然可以用两个树状数组维护。......
  • 题解 P5426 [USACO19OPEN]Balancing Inversions G
    来一篇简单易懂的良心题解。由于数值不是\(0\)就是\(1\),我们可以考虑将逆序对的统计方式化简。以左区间为例,设\(x\)为\(1\)的个数,\(p_i\)为第\(i\)个\(1\)的坐标,则逆序对个数为\(\sum\limits_{i=1}^{x}n-p_i-(x-i)\)。也就是\((n-x)\cdotx+\frac{x\cdot(x+1)}{......
  • 题解 Friendly Spiders
    FriendlySpiders带有技巧的最短路。如果\(u\)能到\(v\),说明\(\gcd(u,v)>1\),也就是有相同因子。所以我们考虑对于每个数\(u\),向他的所有质因子连一条长度为\(1\)的边,这样我们从\(u\)到\(v\)需要走两步,最终答案除以\(2\)即可。每次遇到一个新的因子,都要新建节点。......