HRC 003 T3 置换
HRC 是啥
HZOI Regular Contest
前置知识
\(60\space\text{pts}\) 解法
就像对于一个数,我们经常从素因子之积的角度看待它一样,在这道题中,我们从轮换的角度看待置换。
我们考虑一个轮换变成恒等变换所需次数:一个长度为 \(l\) 轮换,可以看做一个边数为 \(l\) 的环,置换乘法可以看做数字沿着边转一圈。假设某一条边的终点处的数字一开始为 \(k\),那么这条边的起点的位置编号一定是 \(k\)。以轮换 \((3,1,2)\) 为例,边 \(3\rightarrow1\)(位置编号)为例,起点的位置编号为 \(3\),终点一开始是 \(3\)。也就是说,一个数字从它开始的位置到达恒等变换的位置(位置编号也为这个数字),需要经过 \(l-1\) 条边,反映到幂次上,贡献为 \(l\)。
而对于整个置换,若变成恒等置换,则贡献为每个轮换长度的最小公倍数。答案即为每一种最小公倍数的平方与置换总数,即 \(n!\) 的商。
我们考虑统计出每一种最小公倍数的出现次数,设 \(dp_{i,j}\) 为将 \(i\) 个位置分配给若干个长度之最小公倍数为 \(j\) 的方案数。
先来考虑局部转移:将 \(dp_{i,j}\) 转移给 \(dp_{i+k\cdot s,\operatorname{lcm}(j,s)}\),即在已经分配的 \(i\) 个位置中再加入 \(k\) 个长度为 \(s\) 的轮换。对于位置的分配,可以转化为多重集 \(\{i\cdot a_1,s\cdot a_2, \cdots ,s\cdot a_{k+1}\}\) 的排列数,即 \(\frac{(i+k\cdot s)!}{{s!}^k i!}\)。而对于编号的分配,也就是置换环的边“交错情况”的分配,考虑第一个点有 \((n-1)\) 种终点,第二个有 \((n-2)\) 种,以此类推,单个置换环有 \((s-1)!\) 种,共 \(((s-1)!)^k\) 种。这一项与上一项合并得到 \(\frac{(i+k\cdot s)!}{s^k i!}\)。此外,由于每个置换环位置确定后内容也确定,需要消去几个置换环仅仅互换位置的情况,即除以 \(k!\),得到:
\]
同时看到,在整体的转移过程中,得到 \(dp_{i,j}\) 时必有分子 \(i!\),这必将在下一步被消去,留下的 \(n!\) 也将在统计答案时因平均被消去,所以每次转移的系数被消为 \(\frac{1}{s!^k k!}\) ,这样最后不必再除 \(n!\)。
部分分代码
#include<bits/stdc++.h>
using namespace std;
unordered_map<long long,long long> dp[300];
unordered_map<long long,bool> vis[300];
long long n,p,t[300],tmaxn[300],inv[100100],invt[300];
long long gcd(long long a,long long b){
long long tem=0;
for(;a;){
tem=a;
a=b%a;
b=tem;
}
return b;
}
long long lcm(long long a,long long b){
return a*b/gcd(a,b);
}
long long qpow(long long x,long long y){
if(!y) return 1;
long long tem=qpow(x,y>>1);
if(y&1) return x*tem%p*tem%p;
else return tem*tem%p;
}
long long finv(long long x){
if(x<min(p,1ll*100100)) return inv[x];
else return qpow(x,p-2);
}
void dfs(long long x,long long y){
if(!x){
if(y>=100100) printf("%lld\n",y);
return;
}
for(int i=1;i<=x;++i) dfs(x-i,lcm(y,i));
}
int main(){
freopen("perm.in","r",stdin);
freopen("perm.out","w",stdout);
scanf("%lld%lld",&n,&p);
inv[0]=1;
inv[1]=1;
for(int i=2;i<min(p,1ll*100100);++i) inv[i]=(p-p/i)*inv[p%i]%p;
t[0]=1;
for(int i=1;i<=n;++i) t[i]=t[i-1]*i%p;
invt[n]=qpow(t[n],p-2);
for(int i=n-1;i;--i) invt[i]=invt[i+1]*(i+1)%p;
invt[0]=1;
long long ans=0;
dp[0][1]=1;
vis[0][1]=1;
for(int s=1;s<=n;++s){
for(int i=n;i>=0;--i){
for(int k=(n-i)/s;k;--k){
for(auto j:vis[i]){
if(!dp[i][j.first]) continue;
vis[i+k*s][lcm(j.first,s)]=1;
long long tem=dp[i][j.first]*finv(qpow(s,k))%p*invt[k]%p;
dp[i+k*s][lcm(j.first,s)]=(dp[i+k*s][lcm(j.first,s)]+tem)%p;
}
}
}
}
for(auto i:vis[n]) ans=(ans+dp[n][i.first]*(i.first)%p*(i.first)%p)%p;
printf("%lld",ans);
return 0;
}
正解
我们发现由于 \(\operatorname{lcm}\) 结果很多,上面做法会超时,考虑如果某个大于 \(\sqrt{n}\) 的因子在 \(\operatorname{lcm}\) 中出现的次数大于 \(1\) 则这个长度必定大于等于这个因子的平方,也大于 \(n\)。因此,每种这样的因子最多只会出现一次。我们把剩下的因子结合起来,将每种 \(\operatorname{lcm}\) 转换为这几个因子的 \(\operatorname{lcm}\) 复合上其它因子的出现情况。每次将前者作为状态,将后者相同的同时转移,枚举复合上的前者,正常转移后将 \(dp\) 的变化量乘上后者的平方(这样后面枚举的值会和前面复合),统计答案时再复合上前者平方即可。
正解
#include<bits/stdc++.h>
using namespace std;
const long long pr[6]={2,3,5,7,11,13};
struct node{
int pos,lft;
friend bool operator < (node x,node y){
return x.lft<y.lft;
}
}data[300];
long long n,p,base[3000],bcnt,t[300],dp[300][9000],up[300][9000],maxn[300],inv[100100],invt[300];
long long gcd(long long a,long long b){
long long tem=0;
for(;a;){
tem=a;
a=b%a;
b=tem;
}
return b;
}
inline long long lcm(long long a,long long b){
return a*b/gcd(a,b);
}
long long qpow(long long x,long long y){
if(!y) return 1;
long long tem=qpow(x,y>>1);
if(y&1) return x*tem%p*tem%p;
else return tem*tem%p;
}
void dfs(int now,long long tim,long long sum){
if(now==6){
++bcnt;
base[bcnt]=tim;
return;
}
dfs(now+1,tim,sum);
long long tem=1;
for(;;){
tem*=pr[now];
if(sum+tem>n) break;
dfs(now+1,tim*tem,sum+tem);
}
}
int main(){
freopen("perm.in","r",stdin);
freopen("perm.out","w",stdout);
scanf("%lld%lld",&n,&p);
dfs(0,1,0);
sort(base+1,base+bcnt+1);
for(int i=1;i<=n;++i){
data[i].pos=i;
data[i].lft=i;
for(int j=0;j<6;++j){
for(;!(data[i].lft%pr[j]);) data[i].lft/=pr[j];
}
}
sort(data+1,data+n+1);
inv[0]=1;
inv[1]=1;
for(int i=2;i<min(p,1ll*100100);++i) inv[i]=(p-p/i)*inv[p%i]%p;
long long ans=0;
dp[0][1]=1;
for(int lft=1,now=1;lft<=n;lft=now){
for(int i=0;i<=n;++i){
for(int j=1;j<=bcnt;++j) up[i][j]=dp[i][j];
}
long long timr=data[lft].lft*data[lft].lft%p;
for(;data[lft].lft==data[now].lft;++now){
int s=data[now].pos;
for(int j=bcnt;j;--j){
int temlcm=lower_bound(base+1,base+bcnt+1,lcm(base[j],data[now].pos/data[now].lft))-base;
if(temlcm>bcnt) continue;
for(int i=n-s;i>=0;--i){
if(!up[i][j]) continue;
int temi=i+s;
long long div=inv[s];
for(int k=1;temi<=n;++k,temi+=s,div=div*inv[s]%p*inv[k]%p) up[temi][temlcm]=(up[temi][temlcm]+up[i][j]*div)%p;
}
}
}
for(int i=0;i<=n;++i) for(int j=1;j<=bcnt;++j) dp[i][j]=(dp[i][j]+timr*(up[i][j]-dp[i][j]))%p;
}
for(int i=1;i<=bcnt;++i) ans=(ans+dp[n][i]*base[i]%p*base[i])%p;
ans=(ans+p)%p;
printf("%lld",ans);
return 0;
}
HRC 003 T3 置换的更多相关文章
- 2018.09.15模拟总结(T1,T3)
过了一周,终于迎来了第二次模拟(这不是期待的语气),看第一周毒瘤程度,我就觉得接下来的模拟只能更毒瘤. 花了10多分钟读完了三道题,觉得暴力还是挺好写的,然后在每一道题都思索那么几分钟后,觉得还是写暴 ...
- [LeetCode] LFU Cache 最近最不常用页面置换缓存器
Design and implement a data structure for Least Frequently Used (LFU) cache. It should support the f ...
- [LeetCode] Longest Repeating Character Replacement 最长重复字符置换
Given a string that consists of only uppercase English letters, you can replace any letter in the st ...
- 页置换算法FIFO、LRU、OPT
页置换算法FIFO.LRU.OPT 为什么需要页置换 在地址映射过程中,若在页面中发现所要访问的页面不再内存中,则产生缺页中断.当发生缺页中断时操作系统必须在内存选择一个页面将其移出内存,以便为即将调 ...
- [NOIP2016]愤怒的小鸟 D2 T3 状压DP
[NOIP2016]愤怒的小鸟 D2 T3 Description Kiana最近沉迷于一款神奇的游戏无法自拔. 简单来说,这款游戏是在一个平面上进行的. 有一架弹弓位于(0,0)处,每次Kiana可 ...
- POJ2369 Permutations(置换的周期)
链接:http://poj.org/problem?id=2369 Permutations Time Limit: 1000MS Memory Limit: 65536K Total Submi ...
- POJ1026 Cipher(置换的幂运算)
链接:http://poj.org/problem?id=1026 Cipher Time Limit: 1000MS Memory Limit: 10000K Total Submissions ...
- 在Excel中把横行与竖列进行置换、打勾号
在Excel中把横行与竖列进行置换:复制要置换的单元,在新的单元上右键->选择性复制,会出现对话框,选中“置换”,即可在Excel中打勾号,左手按住ALT不放,右手在小键盘也就是右边的数字键盘依 ...
- 操作系统页面置换算法(opt,lru,fifo,clock)实现
选择调出页面的算法就称为页面置换算法.好的页面置换算法应有较低的页面更换频率,也就是说,应将以后不会再访问或者以后较长时间内不会再访问的页面先调出. 常见的置换算法有以下四种(以下来自操作系统课本). ...
- [Operate System & Algorithm] 页面置换算法
页面置换算法是什么?我们看一下百度百科对页面置换算法给出的定义:在地址映射过程中,若在页面中发现所要访问的页面不在内存中,则产生缺页中断.当发生缺页中断时,如果操作系统内存中没有空闲页面,则操作系统必 ...
随机推荐
- C# Socket 使用教程
我在学习Socket时,总是感觉文章看不懂,视频又好长,所以留下这篇学习笔记,权当做同学间学习参考,与个人回顾吧. 简介 Socket(译做:管道/套接字)是一个便捷的类 用于封装通信时所涉及到复杂底 ...
- 【网络攻防】ARP欺骗实验
实验概述 ARP欺骗是一类地址欺骗类病毒,属于木马病毒,自身不具备主动传播的特性,不会自我复制.但是由于其发作的时候会不断向全网发送伪造的ARP数据包,导致网络无法正常运行,严重的甚至可能带来整个网络 ...
- 学习unigui【20】unistringGrid
做成下面效果图: 采用unistringGrid控件. 问题: 1.不同的日期区间如何得到.如: 项目 开始时间时间 -- 终止使用时间 呼吸机 yyyy-mm-dd yyyy-mm-dd ...
- unigui的demo-\Demos\Desktop\DBAppDemo\SimpleDemo.dproj【11】
这个demo很简单. 一个客户表,还有一个票据主从表. 看程序界面: 包括数据提交,彻头彻尾的c/s程序.你完全按照传统的C/S程序模式做开发就可.好处是效率.效率.还是效率! 你还有什么不满意!如果 ...
- 记录一次ubuntu软件安装未完全的解决
背景 预想是在ubuntu20.10上去安装android-studio,所以找了个教程,是使用ubuntu-make来进行安装,不过我也不知为何,安装到最后,出现了dpkg的报错并返回,错误提示是让 ...
- 使用傅里叶级数和Python表示方波
引言 在信号处理和数字通信中,方波是非常常见的一种波形.方波是一种周期性波形,信号在两个固定的幅度之间跳跃,通常是"高"与"低"的状态.你可能会问,如何通过数学 ...
- ESP32S3播放音频文件
ESP32S3播放音频文件 硬件基于立创实战派esp32s3 软件代码基于立创实战派教程修改,分析 播放PCM格式音频 原理图分析 音频芯片ES8311 ES8311_I2C_ADD:0x18 音频功 ...
- Spring基于注解的AOP事务控制
Spring基于注解的AOP事务控制 源码 代码测试 pom.xml <?xml version="1.0" encoding="UTF-8"?> ...
- 代码随想录第三天 | Leecode 203. 移除链表元素、707. 设计链表、206. 翻转链表
Leecode 203 移除链表元素 题目链接:https://leetcode.cn/problems/remove-linked-list-elements/ 题目描述 给你一个链表的头节点 he ...
- Error: A JNI error has occurred, please check your installation and try again Exception in thread "main" java.lang.UnsupportedClassVersionError: HelloWorld has been compiled by a more
一个新手容易遇到的问题,电脑上装了多个版本的java,比如8和11,导致javac和java的版本不一样 在控制面板里将其他版本卸载,留个8就行 然后在环境变量里重新配置一下就ok