记录编号 601051 评测结果 AAAAAAAAAAAAAAAAAAAA
题目名称 1374.[NOI 2011]兔农 最终得分 100
用户昵称 GravatarLikableP 是否通过 通过
代码语言 C++ 运行时间 0.942 s
提交时间 2025-05-27 21:23:27 内存使用 9.51 MiB
显示代码纯文本
#include <cstdio>
#include <cstring>
#define min(__x, __y) [&](auto _x, auto _y) {return _x < _y ? _x : _y;} (__x, __y)
typedef long long ll;

const int MAX = 1e6 + 10;
const int SIZE = 3;

ll p;

struct Matrix {
  ll o[SIZE + 1][SIZE + 1];

  Matrix() {
    memset(o, 0, sizeof o);
  }

  ll* operator [](int val) {
    return o[val];
  }
} mat, tr1, tr2, tr;

Matrix operator * (Matrix x, Matrix y) {
  Matrix res;
  for (int k = 1; k <= SIZE; ++k) {
    for (int i = 1; i <= SIZE; ++i) {
      for (int j = 1; j <= SIZE; ++j) {
        res[i][j] = (res[i][j] + x[i][k] * y[k][j] + p) % p;
      }
    }
  }
  return res;
}

Matrix kasumi(Matrix a, ll b) {
  Matrix res;
  for (int i = 1; i <= SIZE; ++i) res[i][i] = 1;

  while (b) {
    if (b & 1) res = res * a;
    b >>= 1;
    a = a * a;
  }
  return res;
}

ll exgcd(ll a, ll b, ll &x, ll &y) {
  if (b == 0) {
    x = 1;
    y = 0;
    return a;
  }
  ll d = exgcd(b, a % b, x, y);
  ll z = x;
  x = y;
  y = z - (a / b) * y;
  return d;
}

ll getinverse(ll a, ll mod) {
  ll x, y;
  if (exgcd(a, mod, x, y) != 1) return -1;
  return (x % mod + mod) % mod;
}

ll n, k;
ll len[MAX]; // len[i] : 以数字 i 开头的段的长度
ll f[6 * MAX]; // f[i] : 正常的斐波那契数列第 i 位
ll kcnt;
ll seq[MAX]; // seq[i] : 第 i 段段开头的数
ll vis[MAX]; // vis[i] : 数字 i 作为开头在第几个段
bool flag;

int main() {
  freopen("noi2011_rabbit.in", "r", stdin);
  freopen("noi2011_rabbit.out", "w", stdout);
  scanf("%lld %lld %lld", &n, &k, &p);

  if (n == 1 || n == 2) {
    printf("1\n");
    return 0;
  }

  memset(len, 0x7f, sizeof len); // 初始化 len 数组为无穷大
  f[1] = f[2] = 1; // 边界 f[1] = f[2] = 1

  for (ll i = 3; ; ++i) {
    f[i] = (f[i - 1] + f[i - 2]) % k; // 计算 f[i]
   // printf("i=%lld f[i]=%lld\n", i, f[i]);
    if (f[i] % k == 1 && len[1] == 0x7f7f7f7f7f7f7f7f) len[1] = i; // 需要特殊计算 1 开头的段长度
    if (f[i] == 1 && f[i - 1] == 1) break;
    ll inv = getinverse(f[i], k);
    if (inv != -1) len[inv % k] = min(len[inv % k], i);
  }
  //for (ll i = 1; i <= k - 1; ++i) {
  //  printf("len[%lld]=%lld\n", i, len[i]);
  //}

  ll now = 1, tot = 0;
  while (1) {
    seq[++kcnt] = now; // 记录第 kcnt 个段开头的数为 now
    vis[now] = kcnt; // 记录数字 now 作为开头在第 kcnt 个段
    if (len[now] == 0x7f7f7f7f7f7f7f7f) { // 没有逆元
      for (ll i = 1; i < kcnt; ++i) tot += len[seq[i]]; // 计算之前段的总长
      flag = 1; // 记录没有逆元
      break;
    }

    now = (now * f[len[now] - 1]) % k; // 跳转到段的结尾前那个数(或者说是下一段的开头)
    //printf("%lld\n", now);
    if (vis[now]) { // 这个数字之前出现过,找到循环节
      for (ll i = 1; i < vis[now]; ++i) tot += len[seq[i]]; // 计算循环节之前段的总长
      break;
    }
  }

  // 预处理三个矩阵
  mat[1][1] = mat[1][3] = 1;
  tr1[1][1] = tr1[1][2] = tr1[2][1] = tr1[3][3] = 1;
  tr2[1][1] = tr2[1][2] = tr2[2][1] = tr2[3][3] = 1, tr2[3][1] = -1;

  if (n <= tot) { // 没有逆元的段之前或 n 在循环节之前
    len[1]--; // 因为已经算出了 f[1],所以第一段的长度要 -1
    n--; // n 也随之 -1

    for (ll i = 1; i < vis[now]; ++i) {
      if (n >= len[seq[i]]) { // n 在第 i 段之后
        mat = mat * kasumi(tr1, len[seq[i]] - 1) * tr2; // 那么就用快速幂之前算到这一段的结尾
        n -= len[seq[i]];
      } else { // 否则直接乘
        mat = mat * kasumi(tr1, n);
        printf("%lld\n", mat[1][1]);
        return 0;
      }
    }
  } else { // 有循环节
    len[1]--;
    n -= tot; // 直接计算出到循环节之前的数

    for (ll i = 1; i < vis[now]; ++i) mat = mat * kasumi(tr1, len[seq[i]] - 1) * tr2;

    if (flag) { // 没有逆元
      mat = mat * kasumi(tr1, n);
      printf("%lld\n", mat[1][1]);
    } else {
      ll loopLen = 0; // loopLen : 循环节的长度
      for (int i = 1; i <= SIZE; ++i) tr[i][i] = 1;
      for (ll i = vis[now]; i <= kcnt; ++i) { // 计算出循环节的转移矩阵
        tr = tr * kasumi(tr1, len[seq[i]] - 1) * tr2;
        loopLen += len[seq[i]];
      }
      ll tmp = n / loopLen; // 算一下 n 中有多少个完整的循环节
      mat = mat * kasumi(tr, tmp);
      n = n - loopLen * tmp; //剩下的暴力算

      for (ll i = vis[now]; i <= kcnt; ++i) {
        if (n >= len[seq[i]]) { // 假设 n 还够完整的一段
          mat = mat * kasumi(tr1, len[seq[i]] - 1) * tr2;
          n -= len[seq[i]];
        } else {
          mat = mat * kasumi(tr1, n);
          printf("%lld\n", mat[1][1]);
          return 0;
        }
      }
    }
  }
  return 0;
}