[atcoder 367 e] Permute K times

题目原文

题目简述

你有两个个长度为 n n n 的数组 a a a b b b,现在执行 k k k 次以下操作:

构造一个新的序列 c c c,使得 c i = a b i c_i = a_{b_i} ci=abi,再将 a a a 设为 c c c

输出数组 a a a 执行 k k k 次后的结果。

输入格式

输入共 3 3 3 行。
第一行两个整数 n n n k k k,表示数组长度和操作次数。
第二行 n n n 个整数,表示数组 a a a
第三行 n n n 个整数,表示数组 b b b

n k
a1 a2 …… an
b1 b2 …… bn

输出格式

输出一行 n n n 个整数 a 1 ∼ a n a_1 \sim a_n a1an,以空格分开。

a1 a2 …… an

数据范围

1 ≤ n ≤ 2 × 1 0 5 1 \le n \le 2 \times 10^5 1n2×105
0 ≤ k ≤ 2 18 0 \le k \le 2^{18} 0k218
1 ≤ a i ≤ 2 × 1 0 5 1 \le a_i \le 2 \times 10^5 1ai2×105
1 ≤ b i ≤ n 1 \le b_i \le n 1bin

思路

最先想到的就是直接进行 k k k 次操作,一定会超时。

k k k 很大,因此我们可以考虑将其拆分成几段进行计算。

因此我们会想到 s t st st 表。

我们先忽略 a a a 里面存的值,将下标记在 t t t 数组里。

进行 1 1 1 次操作, t t t 变为 b 1 , b 2 , … , b n b_1,b_2,\dots,b_n b1,b2,,bn
进行 2 2 2 次操作, t t t 变为 b b 1 , b b 2 , … , b b n b_{b_1}, b_{b_2}, \dots,b_{b_n} bb1,bb2,,bbn,相当于两次 1 1 1 操作。
进行 3 3 3 次操作,相当于 1 + 2 1+ 2 1+2 操作。
进行 4 4 4 次操作,相当于两次 2 2 2 操作。

s t i , j st_{i,j} sti,j 表示从第 i i i 个位置,走 2 j 2^j 2j 步走到那个位置。
j > 0 j > 0 j>0 时, s t i , j = s t s t i , j − 1 , j − 1 st_{i,j} = st_{st_{i, j - 1}, j - 1} sti,j=ststi,j1,j1
j = 0 j = 0 j=0 时, s t i , 0 = b i st_{i,0} = b_i sti,0=bi

当计算 i i i 位置执行 k k k 次操作后下标的值时,先把 k k k 进行二进制分解,如果第 j j j 位为 1 1 1,则 i = s t i , j i = st_{i, j} i=sti,j

最后输出 a i a_i ai 即可。

#include<bits/stdc++.h>
using namespace std;
long long n, k;
<s 表示 st 数组,f 是预处理 2^i 的值,p 记录 k 二进制分解后的值>
<ans 记录第 i 个位置执行 k 次后的值>
long long a[200010];
long long s[64][200010];
long long p[64], f[64];
int ans[200010];
int main(){
    scanf("%lld %lld", &n, &k);
    for(int i = 1; i <= n; ++ i){
        scanf("%lld", &s[0][i]);
    }
    for(int i = 1; i <= n; ++ i){
        scanf("%lld", &a[i]);
    }
    for(int i = 1; i < 63; ++ i){
        for(int j = 1; j <= n; ++ j){
            s[i][j] = s[i - 1][s[i - 1][j]];//走 2^j 步相当于走两次 2^(j-1) 步
        }
    }
    <二进制分解>
    f[0] = 1;
    for(int i = 1; i < 63; ++ i){
        f[i] = f[i - 1] * 2;
    }
    for(int i = 63; i >= 0; -- i){
        if(k >= f[i]){
            k -= f[i];
            p[i] = 1;
        }
    }
    for(int i = 1; i <= n; ++ i){
        ans[i] = i;//最初位置都为 i
    }
    for(int i = 0; i < 63; ++ i){
        if(!p[i]){
            continue;
        }
        //第 i 位为 1
        for(int j = 1; j <= n; ++ j){
            ans[j] = s[i][ans[j]];
        }
    }
    for(int i = 1; i <= n; ++ i){
        printf("%lld ", a[ans[i]]);
    }
    return 0;
}
  • 15
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值