ACM-矩阵之置换

  • Post author:
  • Post category:其他


还有一类数学题目,那就是置换,其本质就是一一映射,具体参看训练指南144页。可能正如同书中所说的,我们一般处理置换时常常将它们写成循环乘积的形式,这样便于理解和计算。可是如果当我们进行的置换的次数非常多,那么循环乘积可能就会变得比较繁杂,即使任然可以计算,但是在时间上也是不允许的。那么有没有什么方法可以加速置换运算呢?那就是矩阵。首先想这样一个事实,任何置换都可以写成矩阵的形式,如图所示:


上图利用矩阵将{1;2;3;4}置换成了{3;1;2;4},的确是这样的,只要变换第一个矩阵,那么乘上第二个矩阵后的结果就会随着改变,如果我们精心构造,那么有一定会出现我们想要的结果。到这里,方法就很明显了,类是于矩阵求解递推式,我们将第一个矩阵开n次幂,代表数据被置换了n次,然后乘上初始矩阵(原始数据),记为A^n * B = C,最终得到的矩阵里就会出现原始数据被置换了n次之后的数据,那就是我们想要的答案,时间复杂度上依然使用快速幂算法优化,至于如何构造矩阵,这是由题目给出的置换矩阵决定的。下面我们就具体看一道例题,来掌握矩阵求解置换的思想,HDOJ:2371,时空转移(

点击打开链接

),题目如下:

Decode the Strings




Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)

Total Submission(s): 768    Accepted Submission(s): 233







Problem Description
Bruce Force has had an interesting idea how to encode strings. The following is the description of how the encoding is done:

Let x

1

,x

2

,…,x

n

be the sequence of characters of the string to be encoded.

1. Choose an integer m and n pairwise distinct numbers p

1

,p

2

,…,p

n

from the set {1, 2, …, n} (a permutation of the numbers 1 to n).

2. Repeat the following step m times.

3. For 1 ≤ i ≤ n set y

i

to x

pi

, and then for 1 ≤ i ≤ n replace x

i

by y

i

.

For example, when we want to encode the string “hello”, and we choose the value m = 3 and the permutation 2, 3, 1, 5, 4, the data would be encoded in 3 steps: “hello” -> “elhol” -> “lhelo” -> “helol”.

Bruce gives you the encoded strings, and the numbers m and p

1

, …, p

n

used to encode these strings. He claims that because he used huge numbers m for encoding, you will need a lot of time to decode the strings. Can you disprove this claim by quickly decoding the strings?


Input
The input contains several test cases. Each test case starts with a line containing two numbers n and m (1 ≤ n ≤ 80, 1 ≤ m ≤ 10

9

). The following line consists of n pairwise different numbers p

1

,…,p

n

(1 ≤ p

i

≤ n). The third line of each test case consists of exactly n characters, and represent the encoded string. The last test case is followed by a line containing two zeros.


Output
For each test case, print one line with the decoded string.


Sample Input
  
  
5 3 2 3 1 5 4 helol 16 804289384 13 10 2 7 8 1 16 12 15 6 5 14 3 4 11 9 scssoet tcaede n 8 12 5 3 4 2 1 8 6 7 encoded? 0 0


Sample Output
  
  
hello second test case encoded?

题意:
给出n个数字,代表一次置换,数字本身是下标的意思,如置换:2 3 1 5 4,代表:将第一个位置上的换成第二个位置上的,将第二个位置上的换成第三个位置上的,将第三个位置上的换成第一个位置上的,将第四个位置上的换成第五个位置上的,将第五个位置上的换成第四个位置上的,但是对于这道题来说,置换的对象是一串字符,自然操作就针对的是每一个字符了,好了,理解了置换,后面就轻松了。题目最后的要求就是在给定置换次数m和经过置换后的结果,求出最初的字符串。
分析:
题目会给出置换的次数m,和字符串经过置换后的最终结果,这里稍微有些不同,题目要求的是通过结果求最初始的字符串。先来想想正常情况,我们可以通过矩阵计算置换,即:A ^m * B = C,A为构造矩阵,B为初始矩阵,这里C是未知的,需要计算的,而在本题中已知的是C,要求的是B,其实变换一下就行:B = C * A^(-m) = (A的逆)^m * C 。我们先来构造矩阵A,首先假定初始矩阵B = {1;2;3;4;5},然后我们先来看第一个测试用例,它给出的转置矩阵为{2;3;1;5;4},其实它就是矩阵C,因为在一次转置运算中,转置矩阵就代表着答案本身,由此我们便可以构造出A了,详见下图:


可能大家就说了,这只是第一个测试用例的构造矩阵呀,的确是的,不同的转置矩阵对应着不同的构造矩阵,那怎么办呢?其实A矩阵是有规律的,不难发现A矩阵中只有0或1,如果我们统一B矩阵为{1;2;3;4;5}的话,那么要使上图中的等式成立,当且仅当A矩阵相对于置换矩阵C的对应行中的对应列为1,其余为0,比如用例一的C为{2;3;1;5;4},那么对应构造矩阵A的第1行第2列为1,第2行第3列为1,第3行第1列为1,第4行第5列为1,第5行第4列为1,所以我们可以利用题目给出的置换矩阵来构造出A,至于A的逆,因为在这里A是特殊的,它是正交矩阵,所以它的逆等于它的转置。如此一来,用快速幂解之即可。

源代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int MAXN = 100;
const int MOD  = 1e5;
struct Mat
{
    int n, m;
    int mat[MAXN][MAXN];
    Mat()
    {
        memset(mat, 0, sizeof(mat));
        n = m = MAXN;
    };
    void init()                                       // 初始化为单位矩阵
    {
        for(int i=1; i<=n; ++i)
            for(int j=1; j<=m; ++j)
                mat[i][j] = (i==j);
    }
    Mat operator * (Mat b)                             // 重载乘法
    {
        Mat c;
        c = Mat();
        c.n = n;
        c.m = b.m;
        for(int i=1; i<=n; ++i)                        // 注意这里从1开始
            for(int j=1; j<=b.m; ++j)
            {
                //if(mat[j][i] <= 0)  continue;        // 剪枝
                for(int k=1; k<=m; ++k)
                {
                    //if(b.mat[i][k] <= 0) continue;   // 剪枝
                    c.mat[i][j] += (mat[i][k]*b.mat[k][j]); //% MOD;
                    //c.mat[i][j] %= MOD;
                }
            }
        return c;
    }
    Mat QuickPow(Mat a, int k)                         // 快速幂
    {
        Mat c;
        c.n = a.n;
        c.m = a.n;
        c.init();                                      // 初始化为单位矩阵
        while(k)
        {
            if(k & 1)
                c = c*a;
            a = a*a;
            k >>= 1;
        }

        return c;
    }
    Mat transpose()                                    // 求矩阵的转置
    {
        Mat c;
        c.n = m;
        c.m = n;
        for(int i=1; i<=n; ++i)
            for(int j=1; j<=m; ++j)
                c.mat[j][i] = mat[i][j];
        return c;
    }
};

int main()
{//freopen("sample.txt", "r", stdin);
    int n, m;
    char ch[100];

    while(~scanf("%d%d", &n, &m) && (n||m))
    {
        Mat ini, base, ans;
        base.n = base.m = n;
        for(int i=1; i<=n; ++i)                       // 初始矩阵
            base.mat[i][1] = i;
        ini.n = ini.m = n;
        for(int i=1; i<=n; ++i)                       // 构造矩阵
        {
            int a;
            scanf("%d", &a);
            ini.mat[i][a] = 1;
        }
        getchar();
        gets(ch+1);
        ini = ini.transpose();                        // 取转置
        ini = ini.QuickPow(ini, m);                   // 求幂次
        ans = ini * base;
        for(int i=1; i<=n; ++i)
            printf("%c", ch[ans.mat[i][1]]);          // 答案为第一列
        puts("");
    }
    return 0;
}

需要说明的是,在这道题目中,虽然中间结果不会很大,但是我为了保险,在最开始的时候还是取了模,但结果是超时,去掉模运算后就过了,说明模还是比较费时的,需慎用。其它类似的题目还有,VOJ:1049。

接下来,我们将讨论矩阵运算的最后一个部分,即仿射变换,同时还将给出有关矩阵的一份比较全面的模版,传送门(

点击打开链接

)。



版权声明:本文为u011787119原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。