置换环
关于置换环我们先从ABC241的E题引入
题面如下:
输入输出样例
输入 #1
5 3
2 1 6 3 1
输出 #1
11
输入 #2
10 1000000000000
260522 914575 436426 979445 648772 690081 933447 190629 703497 47202
输出 #2
826617499998784056
解:
这道题先给定一个n和一个k
再给出A数列,X表示盘子中现有的糖果数
往后执行k次操作,每次操作加入A[x%n]
颗糖
咳咳,经过分析我们可以知道x%n最多只有n个取值,也就是说我们可以预先把这些值都处理出来
例如对于样例
2 1 6 3 1
产生的图为。
所有的操作会产生一条链和一个环,环里的操作都是循环往复的
链上的操作可以单独计算,对于k次操作,我们可以减去链的长度来判断在环上走了多远
核心代码:
const int N=2e5+10;
int n,m,k,a[N],b[N],p[N];
int pre[N],id[N];
void solve(){
//try it again.
cin>>n>>k;
up(0,n-1)cin>>a[o];
id[0]=0;
up(1,n-1)id[o]=-1;//预处理,id==-1的点就是没有被循环过的点
pre[0]=0;//起点
int st=0,ed=0;
up(0,n-1){
pre[o+1]=pre[o]+a[pre[o]%n];//o是操作的次数
if(id[pre[o+1]%n]!=-1){//已经循环过就成环了
st=id[pre[o+1]%n];//第一次前往这个点的点
ed=o+1;//这个点的次数
break;
}
id[pre[o+1]%n]=o+1;//经过这个点更新一次
}
int ans=0;
if(k<=st)ans=pre[k];//未进入循环节,计算链的值
else{
int len=ed-st;//一次循环所用的次数
int val=pre[ed]-pre[st];//一次循环所放进的糖果数
int x1=(k-st-1)/len;//经过的循环数
int x2=(k-st-1)%len;//未走完剩下的步数
ans=x1*val+pre[x2+st+1];//更新答案
}
puts(ans);//得到答案
}
经过刚才的这个题可以对环有一个新的认识
关于更详细的图论建议看严鸽鸽的这篇题解
Educational Codeforces Round 4 E(图论/构造) - 知乎 (zhihu.com)
//上面那个题有些难,可以去严鸽鸽的知乎看讲解
刚刚VP补了一道置换环的题
Codeforces Round #842 (Div. 2)
Problem - D - Lucky Permutation
题意大致是这样的:
给你一个数组,一次操作可以给数组的两个元素交换位置,请问需要多少次操作才能够让数组中仅存在一对逆序对
//PS:有一个小性质 一个数组中逆序对的数量就是将其还原到顺序所需的操作次数
//这个题也有一个特殊的性质,下面来讲一下(因为比较懒所以就直接引用严鸽鸽的了哈哈哈)
考虑置换环,也就是 i↔p[i] 之间连边。
例如以下排列 [1,7,5,8,2,6,3,4]
我们看看那个4个点的环
在排列上,进行交换
执行了3次交换,就可以使得4个数,到 [1,2,3,4,5...n] 相应的位置上。
如果有cur
个环,那么只需要 n−cur
次交换就可以把排列变成 [1,2,3...n]
了。
然后执行一次相邻交换的操作就可以了。
但是,还有一种情况。
对于上面的4个点的环,我们这样交换
此时,我们交换了两次,而 p[2]
,p[3]
构成了一个逆序对。
也就是,如果环中,有相邻的两个点,那么可以通过减少一次交换,使得其贡献出一个逆序对。
此时的操作次数为 n−cur−1
//那么我们得到了一条很重要的性质
//如果一个数组中的任一置换环中有相邻的元素,那么将其还原到仅留一个逆序对所需的次数就是n-置换环的数量-1
//反之如果没有的话,那么所需的次数就是n-置换环的数量+1 (因为还原顺序以后就需要单独造出一对逆序对)
下面是这道题的主要代码
const int N=2e5+10;
int n,m,k,a[N],b[N],p[N];
bool vis[N];//计算所有的环
map<int,bool>mp;//计算每一个环
bool flag;
void dfs(int u){//搜环的数量
vis[u]=true;
mp[u]=true;
if(mp[u-1]||mp[u+1])flag=true;
if(!vis[a[u]])dfs(a[u]);
}
void solve(){
//try it again.
flag=false;
cin>>n;
up(1,n)vis[o]=false;
up(1,n)cin>>a[o];
int cnt=0;
up(1,n){
if(!vis[a[o]]){
cnt++;
mp.clear();
dfs(a[o]);
}
}
int gg=0;
if(flag)cout<<n-cnt-1<<endl;
else cout<<n-cnt+1<<endl;
}
补了一道div3 F的置换环
Codeforces Round #797 (Div. 3)
题意大致是这样的
给你一个n
,再给出长度为n
的一个字符串和长度为n
的数组
每一次操作都是让s[p[i]]=s[i]
请问要多少次操作才可以让字符串还原
这个题有一个难点就是 在进行完一次循环之前仍有可能将字符串复原
我因为不知道如何实现这个操作因而WA 2了哈哈
就比如这样的一个置换环,进行一次循环需要六次,但因为他一部分和另一部分相等,所以仅需要三次操作就可以复原
又比如这样的一个置换环,进行一次循环需要六次,同上,仅需要两次操作就可以复原
这里严鸽鸽使用的是list来判断
贴出核心代码
if(!vis[i]){
c.clear();
dfs(i);
list<char>preS,nowS;
for(auto id:c)preS.pb(s[id]);
nowS=preS;
nowS.pb(nowS.front());
nowS.pop_front();
int res=1;
while(preS!=nowS){
res++;
nowS.pb(nowS.front());
nowS.pop_front();
}
ans=lcm(ans,res);
}
由此便可以正确解决这个问题
大致代码
const int N=2e5+10;
int n,m,k,a[N],b[N],p[N];
bool vis[N];
string s;
vector<int>c;
int lcm(int a,int b){
return a*b/__gcd(a,b);
}
void dfs(int x){
if(vis[x])return;
vis[x]=1;
c.pb(x);
dfs(p[x]);
}
void solve(){
//try it again.
cin>>n;
cin>>s;
s="?"+s;
up(1,n)cin>>p[o];
up(1,n)vis[o]=0;
int ans=1;
fup(i,1,n){
if(!vis[i]){
c.clear();
dfs(i);
list<char>preS,nowS;
for(auto id:c)preS.pb(s[id]);
nowS=preS;
nowS.pb(nowS.front());
nowS.pop_front();
int res=1;
while(preS!=nowS){
res++;
nowS.pb(nowS.front());
nowS.pop_front();
}
ans=lcm(ans,res);
}
}
puts(ans);
}
本文作者:liangqianxing
本文链接:https://www.cnblogs.com/liangqianxing/p/17045217.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步