浏览器标题切换
浏览器标题切换end
把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

LOJ#3104「TJOI2019」甲苯先生的字符串

题目描述

一天小甲苯得到了一条神的指示,他要把神的指示写下来,但是又不能泄露天机,所以他要用一种方法把神的指示记下来。

神的指示是一个字符串,记为字符串 \(s_1\)\(s_1\) 仅包含小写字母 \(\texttt{a-z}\)

现在小甲苯想要写下神的指示,记为字符串 \(s_2\)\(s_2\) 仅包含小写字母 \(\texttt{a-z}\),要求 \(s_1\) 中的相邻的两个字母不能在 \(s_2\) 中相邻地出现。

现在给定 \(s_2\) 的长度,小甲苯想知道他有多少种方法可以将神的指示写下来。

输出种类数对 \(10^9 + 7\) 取模的结果。

输入格式

文件的第一行只有一个正整数 \(n\),代表字符串 \(s_2\) 的长度,\(n \le 10^{15}\)

第二行是一个字符串,代表字符串 \(s_1\)\(s_1\) 的长度不超过 \(100000\)

输出格式

输出一个整数,表示小甲苯可以写出的字符串的总数。

结果对 \(10^9 + 7\) 取模。

样例

样例输入

2
ab

样例输出

675

数据范围与提示

对于 \(30\%\) 的数据,\(n ≤ 100000\)

对于 \(100\%\) 的数据,\(n ≤ 10^{15}\)

题解

我的做法其实应该和网上的题解差不多。不过理解起来可能需要比较感性一点?

因为考虑的只有相邻两个字符,不难得知,可以把所有关系写成一个\(26\times 26\)的矩阵。

先思考一下正常怎么求解:\(f[i][j]=\sum f[i-1][k]*a[k][j]\)\(i\)位第几位数字,\(j\)为当前放的字母,\(k\)为上一位放的字母)

那么我们把\(f[i]\)视为一个\(1\times n\)的矩阵,实际上这就是一个矩阵乘法的过程。

利用矩阵快速幂求解即可。

但是我构造的初始的矩阵有点不同:初始矩阵为一个全\(1\)的矩阵,最后统计答案时答案为\(\sum f[i][i]\)

怎么理解呢?实际上就是第一位怎么放都行,然后矩阵乘法得到的矩阵中的\(c[i][j]\),实际上就是第一个矩阵的第\(i\)行与第二个矩阵的第\(j\)列的答案。所以最终的\(f[i][i]\)即为第一位填\(i\)可以得到的方案数。

那么为什么要这么构造就不难理解了。

#include <bits/stdc++.h>
using namespace std;

#define ll long long
const int N = 100010;
const ll mod = 1e9 + 7;

ll n;
char s[N];
struct mat {
    ll m[26][26];
};

mat operator * (mat a, mat b) {
    mat c;
    memset(c.m, 0, sizeof(c.m));
    for(int i = 0; i < 26; ++i) {
        for(int j = 0; j < 26; ++j) {
            for(int k = 0; k < 26; ++k) {
                c.m[i][j] = (c.m[i][j] + a.m[i][k] * b.m[k][j] % mod) % mod;
            }
        }
    }
    return c;
}

mat base, ans, now;

int idx(char c) {return c - 'a';} 

void print(mat a) {
    for(int i = 0; i < 26; ++i) {
        for(int j = 0; j < 26; ++j) printf("%d ", a.m[i][j]);
        puts("");
    }
}

int main() {
    scanf("%lld%s", &n, s + 1);
    for(int i = 0; i < 26; ++i) for(int j = 0; j < 26; ++j) base.m[i][j] = 1;
    int len = strlen(s + 1);
    for(int i = 1; i < len; ++i) {
        base.m[idx(s[i])][idx(s[i + 1])] = 0;
    }

    memset(ans.m, 0, sizeof(ans.m));
    memset(now.m, 0, sizeof(now.m));
    for(int i = 0; i < 26; ++i) ans.m[i][i] = 1;

    --n;
    while(n) {
        if(n & 1) ans = ans * base;
        base = base * base; n >>= 1;
    }
    for(int i = 0; i < 26; ++i) for(int j = 0; j < 26; ++j) now.m[i][j] = 1;
    now = now * ans;

    ll sum = 0;
    for(int i = 0; i < 26; ++i) sum = (sum + now.m[i][i]) % mod;
    printf("%lld\n", sum);
}
posted @ 2019-05-19 17:17  henry_y  阅读(236)  评论(1编辑  收藏  举报