AtCoder Beginner Contest 371 G. Lexicographically Smallest Permutation(数论 枚举 扩展CRT思想)

题目

deb8633f1a9444e395c0ffc01e16f3cb.png

思路来源

rui_er代码

题解

不难发现,i->pi形成一个置换环,每个置换环有一个长度,

需要确定一个旋转次数x,使得x%cyc[1]=mn[1],x%cyc[2]=mn[2],等等

其中cyc[1]是从前到后遇到的第一个环的环长,

mn[1]为了将第一个环这个环的最小值旋转到首位时的旋转次数

 

然后再遇到第二个环的时候,需要在满足第一个取模条件成立的约束条件下,

找到符合约束条件的位置的最小值mn[2],再令x%cyc[2]=mn[2],并旋转

重复这个操作直到最后一个环

 

注意到环长最大1e5,几个数乘起来爆了long long,所以暴力excrt解是否存在合法x显然不可行

因为cyc[1]+cyc[2]+...=n,总环长是n,所以当前环长是可枚举的

 

一个直观地观察是,

如果前面已经遇到了一个x%6=1,那么再遇到长为6的环的时候,显然只能选x%6=1

 

事实上,约束是按质因子限制的

可以枚举cyc[i]的每个质因子p,把当前mn[i]%p的值记录下来,记为remain[p]

后面的环长cyc[i+1]对应枚举mn[i+1]的时候,如果再遇到remain[p]的话,不能冲突,

冲突的话,说明这个旋转次数,对应把位置旋到第一位当最小值时,不合法

 

遍历环长个位置,找到每个环的合法位置中最小的,然后进行旋转操作即可

 

根据学弟的问题补充一下说明

1. x%6=1的时候,令x=1+6k,一定有x%2=1,x%3=1

2. 根据CRT,x%2=1,x%3=1能反推出x%6=1,

所以一个约束只需转化成对其所有质因子的约束,质因子的约束能联合其中任意个,还原出对应乘积的原约束

3. 如果说前面我得到了一组不同质因数remain的解,但是到某一个环,发现无论如何都没有解,这种情况是否会发生

前面得到了一组不同质因数remain的解,这意味着至少能反解出来一个x,用这个x去模这个环长得到的数,就是这个环的解

代码

#include <bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<vector>
#include<map>
#include<set>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
typedef long long ll;
typedef double db;
typedef pair<int,int> P;
#define fi first
#define se second
#define pb push_back
#define dbg(x) cerr<<(#x)<<":"<<x<<" ";
#define dbg2(x) cerr<<(#x)<<":"<<x<<endl;
#define SZ(a) (int)(a.size())
#define sci(a) scanf("%d",&(a))
#define pt(a) printf("%d",a);
#define pte(a) printf("%d\n",a)
#define ptlle(a) printf("%lld\n",a)
#define debug(...) fprintf(stderr, __VA_ARGS__)
using namespace std;
const int N=2e5+10;
vector<int>fac[N];
int n,p[N],a[N],ans[N],rem[N];
bool vis[N];
int gcd(int x,int y){return !y?x:gcd(y,x%y);}
void sol(){
    memset(rem,-1,sizeof rem);
    for(int i=1;i<N;i++){
        for(int j=i;j<N;j+=i){
            fac[j].pb(i);
        }
    }
    sci(n);
    rep(i,1,n)sci(p[i]);
    rep(i,1,n)sci(a[i]);
    rep(i,1,n){
        if(p[i]==i){ans[i]=a[i];continue;}
        if(vis[i])continue;
        int mn=n+1,nc,c=0;
        vector<int>tmp;
        for(int j=i;!vis[j];j=p[j],c++){
            tmp.pb(j);
            vis[j]=1;
        }
        //printf("mn:%d nc:%d\n",mn,nc);
        int sz=SZ(tmp);//x%sz=nc
        rep(j,0,sz-1){
            bool ok=1;
            for(auto &d:fac[sz]){
                if(rem[d]==-1)continue;
                if((j%d)!=rem[d]){
                    ok=0;
                    break;
                }
            }
            if(!ok)continue;
            if(mn>a[tmp[j]])mn=a[tmp[j]],nc=j;
        }
        rep(j,0,sz-1){
            ans[tmp[j]]=a[tmp[(j+nc)%sz]];
        }
        for(auto &d:fac[sz]){
            rem[d]=nc%d;
        }
    }
    rep(i,1,n){
        printf("%d%c",ans[i]," \n"[i==n]);
    }
}
int main(){
    sol();
    return 0;
}
/*
1->3
2->1
3->5
5->2
4 3 1 6
1 4 2 5 
ai=api
1 2 3 4
1 2 3 4 5 6
gcd(4,6)=2
*/

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值