置换群快速幂运算

本文介绍了如何运用置换群的快速幂运算解决序列变换问题,包括POJ 1721 CARDS和POJ 3590的解题思路。讨论了奇数和偶数长度循环在平方运算后的性质,并提出了解决方案。重点在于理解置换的平方运算对循环长度的影响,以及如何找到目标序列对应的源序列。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

强烈推荐阅读潘震皓的《置换群快速幂运算》,阅读完之后可以尝试做下面的三道题目~~


POJ 1721  CARDS

这道题目在上面的论文中已经讲述了,因为题目的每一次洗牌便是当前的置换的一次的平方运算,因此经过S次洗牌之后,相当于做了(T)^(2^S)的运算,由于题目中已经假设原来的牌的数目为奇数,因此对于置换的平方运算gcd(n,2)=1,因此对于每次平方运算置换内的循环是不会产生分裂的,仍旧为一个大循环。我们知道对于一个长度为n的循环T^n整数幂运算后,置换变成了恒等置换,因此在计算(T)^(2^S)的运算时,只需要计算(T)^((2^S)%(2^A))即可(2^A=1(mod n))。题目给出的是目标序列和洗牌的次数S,求解原先的序列即(T)^(1/(2^S)),即置换的分数幂运算,由上面我们知道((T)^(1/2^S))^(2^A)仍为该置换,因此我们可以将上述的式子变成(T)^(2^(A-S%A))的整数幂运算,这便是原先的牌的序列。对于平方运算我们直接采用模拟的方法求解即可~~


#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int elem[1010], t_elem[1010];
int N, S, A;

int power_mod(int a, int b, int c)
{
    int ret = 1;
    while(b)
    {
        if(b & 1) ret = ret * a % c;
        a = a * a % c;
        b >>= 1;
    }
    return ret;
}

void solve(int cnt)
{
    for(int i = 1; i <= cnt; ++i)
    {
        for(int j = 1; j <= N; ++j)
        {
            t_elem[j] = elem[elem[j]];
        }
        for(int j = 1; j <= N; ++j)
        {
            elem[j] = t_elem[j];
        }
    }
}

int main()
{
    //freopen("aa.in", "r", stdin);
    //freopen("bb.out", "w", stdout);

    while(scanf("%d %d", &N, &S) != EOF)
    {
        for(int i = 1; i <= N; ++i)
        {
            scanf("%d", &elem[i]);
        }
        for(A = 1; A <= N && power_mod(2, A, N) != 1; ++A);

        int time = A - (S % A);
        solve(time);
        for(int i = 1; i <= N; ++i) printf("%d\n", elem[i]);
    }
    return 0;
}



POJ 3128    Leonardo's Notebook

题目给出一个含有26个字母的序列,问是否能够被另一个序列经过一定的变换转化过来,其实给出一个目标置换D,是否存在T,使得T^2 = D。

这需要用到上面论文的知识,分析了两个测试样例。可以知道对于源置换内的一个奇数长度的循环在目标置换的长度是不发生改变的(gcd(奇数,2)=1),对于源置换的一个偶数长度的置换在经过平方运算之后,分解成两个长度相等的偶(奇)循环。可以看出对于目标置换中的一个长度为奇数的循环,我们可以假设源置换中存在一个长度为奇数的循环与之对应,因此对于奇数长度的循环我们可以忽略,因为总是存在解的。因此我们只考虑偶数长度的循环,因为偶数长度的循环肯定是由源置换中的一个长度为偶数的置换分裂而来的,因此我们只需要验证对于目标置换中的长度为每一个偶数的循环的个数是否为偶数个即可~~


#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
bool visit[30];
char str[30];
int  xsize[30];


int main()
{
    freopen("aa.in", "r", stdin);
    freopen("bb.out", "w", stdout);

    int T;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%s", str + 1);
        memset(visit, false, sizeof(visit));
        memset(xsize, 0, sizeof(xsize));
        for(int i = 1; i <= 26; ++i)
        {
            if(!visit[i])
            {
                int tmp = 0, j = i;
                while(!visit[j])
                {
                    visit[j] = true;
                    tmp++;
                    j = (str[j]-'A'+1);
                }
                xsize[tmp]++;
            }
        }
        bool flag = true;
        for(int i = 2; i <= 26; i += 2)
        {
            if(xsize[i] % 2)
            {
                flag = false;
                break;
            }
        }
        if(flag)
            printf("Yes\n");
        else
            printf("No\n");
    }
    return 0;
}

POJ 3590 The shuffle Problem

这个题目搞了很长时间,题目思路构思只用了十分钟,但是程序写了两个小时,原因出在DP上,因为我没想到怎样去解决a1+a2+a3+...+ak=n,使得lcm(a1, a2, ..., ak)最大。

果断使用DP解决的,自己的做的DP题only几道,平常都是让队友做的,月末把自己挂的剩余的组合数学的做完之后,立刻开启DP和数据模式,花两三个月的时间去仔细研究一下,可是实验室的老师让我学长去研究机器人平台的实现问题,要用到Linux,但是我Linux学的那是一个菜啊,看来接下来的时间得合理安排了~~

设dp[i][j]表示将i分解成j个数的和的最大最小公倍数,dp[i][j] = max(lcm(dp[k][j-1],i-k)) (j-1<=k<=i-1),这个问题看完之后是如此的简单啊,为什么自己没想到呢,弱爆了~~

怎样才能使得最小公倍数的最大的方案的字典序最小呢?

我们知道对于一个循环,假如(1,2,3,...n),它的字典序最小且长度为n的循环为(2,3,,..,n,1),这个只需要稍微证明一下即可。

因此我们只需要求得相应的a1,a2,...,ak,进行相应的构造即可。另外循环的个数越多越好,因为这样构造的字典序最小的方案肯定比循环个数少的字典序的方案最优,另外对于长度越小的循环应该在在前面进行构造。例如一个长度为2和3的循环,显然2在3的前面先构造的话,方案是最优的~~~ 

对于最大的最小公倍数max_lcm,将其分解成p1^a1*p2^a2*...*pk^ak,则循环个数最多的方案为:1*t+k,(t=n-sum(pi^ai)),然后可以进行构造了~~~


#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int MAXN = 110;
const int INF = 0x3f3f3f3f;
typedef long long LL;
bool is_prime[MAXN];
LL prime[MAXN];
LL dp[MAXN][MAXN];
LL facs[MAXN], cnt[MAXN], y_sum[MAXN];
LL max_lcm;
int n, len, y_len;

/*
LL gcd(LL a, LL b)
{
    LL c;
    while(b)
    {
        c = b;
        b = a % b;
        a = c;
    }
    return a;
}
*/

LL gcd(LL a, LL b)
{
    if(b == 0)
        return a;
    return gcd(b, a % b);
}

LL lcm(LL a, LL b)
{
    return a / gcd(a, b) * b;
}

void get_prime_table()
{
    int i, j;
    len = 0;
    memset(is_prime, true, sizeof(is_prime));
    is_prime[0] = is_prime[1] = false;
    prime[len++] = 2;
    for(i = 4; i < MAXN; i += 2)
        is_prime[i] = false;
    for(i = 3; i * i <= MAXN; i += 2)
    {
        if(is_prime[i])
        {
            prime[len++] = i;
            for(j = i * i; j < MAXN; j += i)
            {
                is_prime[j] = false;
            }
        }
    }
    for( ; i < MAXN; ++i)
        if(is_prime[i])
            prime[len++] = i;
}

void solve()
{
    memset(dp, 0, sizeof(dp));
    for(int i = 1; i < MAXN; ++i)
    {
        dp[i][1] = i;
    }
    for(int i = 2; i < MAXN; ++i)
    {
        for(int j = 2; j < MAXN; ++j)
        {
            for(int k = j-1; k < i; ++k)
            {
                dp[i][j] = max(dp[i][j], lcm(dp[k][j-1], i-k));
            }
        }
    }
}

void cal(LL x)
{
    y_len = 0;
    memset(facs, 0, sizeof(facs));
    memset(cnt, 0, sizeof(cnt));

    for(int i = 0; i < len && prime[i] * prime[i] <= x; ++i)
    {
        if(x % prime[i] == 0)
        {
            facs[y_len] = prime[i];
            while(x % prime[i] == 0)
            {
                x /= prime[i];
                cnt[y_len]++;
            }
            y_len++;
        }
    }
    if(x > 1)
        facs[y_len] = x, cnt[y_len++] = 1;
    return ;
}

LL mul(LL a, LL b)
{
    LL ret = 0;
    while(b)
    {
        if(b & 1) ret = ret + a;
        a = a * 2;
        b >>= 1;
    }
    return ret;
}

LL power(LL a, LL b)
{
    LL ret = 1;
    while(b)
    {
        if(b & 1)
            ret = ret * a;
        a = a * a;
        b >>= 1;
    }
    return ret;
}

int main()
{
    //freopen("aa.in", "r", stdin);
    //freopen("bb.out", "w", stdout);

    solve();
    get_prime_table();

    int T;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        max_lcm = -INF;
        for(int i = 1; i <= n; ++i)
        {
            max_lcm = max(max_lcm, dp[n][i]);
        }
        cal(max_lcm);
        LL total = 0, t_cnt = 0;
        for(int i = 0; i < y_len; ++i)
        {
            total += (y_sum[i]=power(facs[i], cnt[i]));
        }
        cout << max_lcm;
        for(int i = 1; i <= n - total; ++i)
        {
            cout << " " << i;
        }
        t_cnt += n - total;
        sort(y_sum, y_sum + y_len);     //////////////
        for(int i = 0; i < y_len; ++i)
        {
            for(int j = 2; j <= y_sum[i]; ++j)
            {
                cout << " " << t_cnt + j;
            }
            cout << " " << t_cnt + 1;
            t_cnt += y_sum[i];
        }
        cout << endl;
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值