Tricks

记录做题时的一些有趣 Tricks

\(\text{Prob.1}\) P3674 小清新人渣的本愿

算法:

莫队、\(\text{bitset}\)

思路

\(S=10^5\)

考虑使用 \(\text{bitset}\)\(O(1)\) 维护当前区间出现的数

\(u,v\) 两个 \(\text{bitset}\) 分别维护 \(x,S-x\) 是否在区间中存在

第一个询问本质上是求区间中是否存在两个数 \(a,a+x\),那么可以将 \(u\) 左移 \(x\) 位再和 \(u\) 按位与,如果结果存在 \(1\),那么答案为真

第二个询问本质上是求区间中是否存在两个数 \(a,x-a\),考虑将 \(v\) 右移 \(S-x\) 位,此时这个 \(\text{bitset}\) 的第 \(i\) 位维护的是 \(x-i\) 是否存在,和 \(u\) 按位与即可

第三个询问,由于 \(x \le S\),可以暴力枚举 \(x\) 的因数,在 \(u\) 中判断是否存在即可

时间复杂度:\(O(n\sqrt{n}+\frac{n^2}{w})\)

Code

struct Q {
  int id, op, l, r, x;
  bool operator<(const Q &o) const {
    return (l / B) != (o.l / B) ? (l / B) < (o.l / B) : (((l / B) & 1) ? r < o.r : r > o.r);
  }
} q[kMaxN];

//  ---------------

cin >> n >> m, B = sqrt(n);
for (int i = 1; i <= n; i++) {
  cin >> a[i];
}
for (int i = 1; i <= m; i++) {
  cin >> q[i].op >> q[i].l >> q[i].r >> q[i].x, q[i].id = i;
}
sort(q + 1, q + m + 1);
for (int i = 1; i <= m; i++) {
  for (; R < q[i].r; c[a[++R]]++, c[a[R]] == 1 && (u.set(a[R]), v.set(kS - a[R]), 0)) {
  }
  for (; L > q[i].l; c[a[--L]]++, c[a[L]] == 1 && (u.set(a[L]), v.set(kS - a[L]), 0)) {
  }
  for (; R > q[i].r; c[a[R]]--, c[a[R]] == 0 && (u.reset(a[R]), v.reset(kS - a[R]), 0), R--) {
  }
  for (; L < q[i].l; c[a[L]]--, c[a[L]] == 0 && (u.reset(a[L]), v.reset(kS - a[L]), 0), L++) {
  }
  if (q[i].op == 1) {
    ret[q[i].id] = (u & (u << q[i].x)).any();
  } else if (q[i].op == 2) {
    ret[q[i].id] = (u & (v >> (kS - q[i].x))).any();
  } else {
    for (int p = 1; p * p <= q[i].x; p++) {
      if (q[i].x % p == 0 && u[p] && u[q[i].x / p]) {
        ret[q[i].id] = 1;
        break;
      }
    }
  }
}
for (int i = 1; i <= m; i++) {
  cout << (ret[i] ? "hana" : "bi") << '\n';
}

\(\text{Prob.2}\) ABC221G Jumping sequence

算法

曼哈顿距离转切比雪夫距离,\(\text{bitset}\) 优化可行性 dp

思路

考虑将坐标系旋转 \(\frac{\pi}{4}\) 并拉长 \(\sqrt{2}\) 倍,那么 \((x,y)\to (x-y,x+y)\),于是:

方向 位移
L \((-d,-d)\)
R \((d,d)\)
U \((-d,d)\)
D \((d,-d)\)

于是问题就转化成了两个形如 \(\pm d_1\pm d_2\pm\cdots\pm d_n=C\)\(C\) 为常数)的问题,可以将两边同时加上 \(\displaystyle\sum_{i=1}^{n}d_i\) 后除以 \(2\),就可以变成一个很典的问题:

是否存在 \(f_i\in\{0,1\}\),使 \(\displaystyle\sum_{i=1}^{n}f_i\times d_i=C\)

\(dp_{i,j}\) 为前 \(i\) 个数能否凑齐 \(j\),那么:

\[dp_{i,j}=dp_{i-1,j} | dp_{i-1,j-d_i} \]

这一部分可以用 \(\text{bitset}\) 优化

时间复杂度:\(O(\frac{nW}{w})\)

Code

cin >> n >> x >> y, tmp = x, x = x - y, y = tmp + y;
for (int i = 1; i <= n; i++) {
  cin >> d[i];
}
if (abs(x) > accumulate(d + 1, d + n + 1, 0) || abs(y) > accumulate(d + 1, d + n + 1, 0) || (x + accumulate(d + 1, d + n + 1, 0)) & 1 || (y + accumulate(d + 1, d + n + 1, 0)) & 1) {
  return cout << "No\n", 0;
}
(x += accumulate(d + 1, d + n + 1, 0)) >>= 1, (y += accumulate(d + 1, d + n + 1, 0)) >>= 1, dp[1][0] = 1;
for (int i = 1; i <= n; i++) {
  dp[i + 1] = dp[i] | (dp[i] << d[i]);
}
if (!dp[n + 1][x] || !dp[n + 1][y]) {
  return cout << "No\n", 0;
}
cout << "Yes\n";
for (int i = n; i; i--) {
  !dp[i][x] && (x -= d[i], ans[i]++), !dp[i][y] && (y -= d[i], ans[i] += 2);
}
for (int i = 1; i <= n; i++) {
  cout << (ans[i] == 0 ? 'L' : ans[i] == 1 ? 'D' : ans[i] == 2 ? 'U' : 'R');
}
cout << '\n';

\(\text{Prob.3}\) P10453 七夕祭

This

\(\text{Prob.4}\) P5926 [JSOI2009] 面试的考验

算法

人类智慧

思路

我们充分发扬人类智慧,先将原序列排序,对于每一个点向左偏移 \(80\) 位,可以得到将近 \(80n\) 个答案候选,将其再次进行排序

对于 \(r-l \le 1000\),可以直接暴力平方枚举,否则从小到大枚举答案候选。对于两个随机区间,当前答案候选被包含的概率是 \(\frac{1}{6}\),即平均枚举不到 \(10\) 次就可以找到符合条件的答案

时间复杂度:\(O(\texttt{rand})\)

Code

struct P {
  int l, r, x;
  bool operator<(const P &o) const { return x < o.x; }
};
struct E {
  int id, x;
  bool operator<(const E &o) const { return x < o.x; }
};

// --------------
cin >> n >> k;
for (int i = 1; i <= n; i++) {
  cin >> e[i], a[i] = {i, e[i]};
}
sort(a + 1, a + n + 1);
for (int i = 1; i <= n; i++) {
  for (int j = max(1, i - 80); j <= i; j++) {
    if (a[i].x != a[j].x) {
      f[++tot] = (P){min(a[i].id, a[j].id), max(a[i].id, a[j].id), a[i].x - a[j].x};
    }
  }
}
sort(f + 1, f + tot + 1);
for (; k; k--) {
  cin >> x >> y;
  if (y - x <= 1000) {
    for (int i = x; i <= y; i++) {
      t[i] = e[i];
    }
    sort(t + x, t + y + 1), ans = 1e9;
    for (int i = x + 1; i <= y; i++) {
      t[i] != t[i - 1] && (ans = min(ans, t[i] - t[i - 1]));
    }
    cout << ans << '\n';
  } else {
    for (int i = 1; i <= tot; i++) {
      if (x <= f[i].l && f[i].r <= y) {
        cout << f[i].x << '\n';
        break;
      }
    }
  }
}

\(\text{Prob.5}\) P4168 [Violet] 蒲公英

算法

分块

思路

首先对数组进行离散化,将数组平均分成 \(B=\sqrt[3]{n}\) 个块,对于每个 \(1\le l\le r\le B\) 求出从第 \(l\) 个块左端点到第 \(r\) 个块右端点中所有数的出现次数和众数

对于每组询问,若 \(l,r\) 在同一块中则暴力找答案,否则从最大的被 \([l,r]\) 包含的连续完整块向左向右扩展,最多扩展 \(2B\) 次,求出答案后再把扩展的部分减去恢复原状

时间复杂度:\(O(n^{\frac{5}{3}})\)

Code

cin >> n >> q, B = ceil(cbrt(n)), sz = n / B;
for (int i = 1; i <= n; i++) {
  cin >> a[i], f[i] = a[i];
}
sort(f + 1, f + n + 1), tot = unique(f + 1, f + n + 1) - f - 1;
for (int i = 1; i <= n; i++) {
  a[i] = lower_bound(f + 1, f + tot + 1, a[i]) - f;
}
for (int i = 1; i <= B; i++) {
  L[i] = R[i - 1] + 1, R[i] = (i * sz > n) ? n : (i * sz);
  i == B && R[i] != n && (R[i] = n);
  for (int j = L[i]; j <= R[i]; j++) {
    b[j] = i;
  }
}
for (int i = 1; i <= B; i++) {
  for (int j = i; j <= B; j++) {
    for (int k = L[i]; k <= R[j]; k++) {
      (++c[i][j][a[k]] > gmx[i][j] || (c[i][j][a[k]] == gmx[i][j] && a[k] < g[i][j])) && (gmx[i][j] = c[i][j][a[k]], g[i][j] = a[k]);
    }
  }
}
for (int mx = 0, ans = 0, lf, rf; q; q--, mx = 0, ans = 0) {
  cin >> l >> r, l = ((l + lst - 1) % n) + 1, r = ((r + lst - 1) % n) + 1, l > r && (swap(l, r), 0), lb = b[l], rb = b[r];
  if (lb == rb) {
    for (int i = l; i <= r; i++) {
      (++c[0][0][a[i]] > mx || (c[0][0][a[i]] == mx && a[i] < ans)) && (mx = c[0][0][a[i]], ans = a[i]);
    }
    cout << (lst = f[ans]) << '\n';
    for (int i = l; i <= r; i++) {
      c[0][0][a[i]]--;
    }
    continue;
  }
  lf = L[lb] == l, rf = R[lb] == r, mx = gmx[lb + 1][rb - 1], ans = g[lb + 1][rb - 1];
  for (int i = l; i <= R[lb]; i++) {
    (++c[lb + 1][rb - 1][a[i]] > mx || (c[lb + 1][rb - 1][a[i]] == mx && a[i] < ans)) && (mx = c[lb + 1][rb - 1][a[i]], ans = a[i]);
  }
  for (int i = L[rb]; i <= r; i++) {
    (++c[lb + 1][rb - 1][a[i]] > mx || (c[lb + 1][rb - 1][a[i]] == mx && a[i] < ans)) && (mx = c[lb + 1][rb - 1][a[i]], ans = a[i]);
  }
  cout << (lst = f[ans]) << '\n';
  for (int i = l; i <= R[lb]; i++) {
    c[lb + 1][rb - 1][a[i]]--;
  }
  for (int i = L[rb]; i <= r; i++) {
    c[lb + 1][rb - 1][a[i]]--;
  }
}

\(\text{Prob.6}\) CF1270F Awesome Substrings

This

\(\text{Prob.7}\) P7735 [NOI2021] 轻重边

算法

树剖,线段树

思路

考虑一颗常规的线段树,存储段内颜色段数量。于是会发现 pushup 操作会很不好做,所以我们存下 \(\text{lc,rc}\) 分别表示区间内左右端点的颜色,再存一个区间颜色段数量

为了方便,可以重载线段树结构体的加法,两个区间的总颜色段数量就是两边加起来,但如果左区间的右端点与右区间的颜色相同,总颜色段数量还要减去 \(1\)

然后就是一个常规的树剖+线段树了

时间复杂度:\(O(n\log^2 n)\)

Code

struct Node {
  int lc, rc, sum, tag;
  Node(int lc_ = 0, int rc_ = 0, int sum_ = 0, int tag_ = 0) { lc = lc_, rc = rc_, sum = sum_, tag = tag_; }
  friend Node operator+(Node l, Node r) { return (Node){l.lc, r.rc, l.sum + r.sum + (l.rc == r.lc), 0}; }
};
struct SegTree {
  Node t[kMaxN << 2];
  void init() {
    for (int i = 0; i < kMaxN << 2; i++) {
      t[i] = (Node){0, 0, 0, 0};
    }
  }
  void pushup(int x, int tmp = 0) { tmp = t[x].tag, t[x] = t[x << 1] + t[x << 1 | 1], t[x].tag = tmp; }
  void addtag(int x, int l, int r, int k) { t[x] = (Node){k, k, r - l, k}; }
  void pushdown(int x, int l, int r) {
    if (t[x].tag) {
      int mid = l + r >> 1;
      addtag(x << 1, l, mid, t[x].tag), addtag(x << 1 | 1, mid + 1, r, t[x].tag), t[x].tag = 0;
    }
  }
  void build(int x, int l, int r) {
    if (l == r) {
      return t[x] = (Node){w[id[l]], w[id[l]], 0, 0}, void();
    }
    int mid = l + r >> 1;
    build(x << 1, l, mid), build(x << 1 | 1, mid + 1, r), pushup(x);
  }
  void update(int x, int l, int r, int L, int R, int k) {
    if (L <= l && r <= R) {
      return addtag(x, l, r, k);
    }
    int mid = l + r >> 1;
    pushdown(x, l, r), L <= mid && (update(x << 1, l, mid, L, R, k), 0), R > mid && (update(x << 1 | 1, mid + 1, r, L, R, k), 0);
    pushup(x);
  }
  Node query(int x, int l, int r, int L, int R) {
    if (L <= l && r <= R) {
      return t[x];
    }
    int mid = l + r >> 1;
    pushdown(x, l, r);
    if (R <= mid) {
      return query(x << 1, l, mid, L, R);
    } else if (L > mid) {
      return query(x << 1 | 1, mid + 1, r, L, R);
    } else {
      return query(x << 1, l, mid, L, R) + query(x << 1 | 1, mid + 1, r, L, R);
    }
  }
} tr;

void DFS(int u, int fa) {
  dep[u] = dep[fa] + 1, sz[u] = 1, f[u] = fa;
  for (int v : g[u]) {
    if (v != fa) {
      DFS(v, u), sz[u] += sz[v], sz[v] > sz[son[u]] && (son[u] = v);
    }
  }
}
void DFS1(int u, int fa, int T) {
  top[u] = T, id[dfn[u] = ++tot] = u;
  son[u] && (DFS1(son[u], u, T), 0);
  for (int v : g[u]) {
    if (v != fa && v != son[u]) {
      DFS1(v, u, v);
    }
  }
}
void updater(int x, int y) {
  for (tot++; top[x] != top[y]; x = f[top[x]]) {
    dep[top[x]] < dep[top[y]] && (swap(x, y), 0);
    tr.update(1, 1, n, dfn[top[x]], dfn[x], tot);
  }
  dep[x] > dep[y] && (swap(x, y), 0);
  tr.update(1, 1, n, dfn[x], dfn[y], tot);
}
int queryr(int x, int y, int ret = 0, Node s = (Node){0, 0, 0, 0}) {
  for (; top[x] != top[y]; x = f[top[x]]) {
    dep[top[x]] < dep[top[y]] && (swap(x, y), 0);
    s = tr.query(1, 1, n, dfn[top[x]], dfn[x]), ret += s.sum + (tr.query(1, 1, n, dfn[top[x]], dfn[top[x]]).lc == tr.query(1, 1, n, dfn[f[top[x]]], dfn[f[top[x]]]).lc);
  }
  dep[x] > dep[y] && (swap(x, y), 0);
  s = tr.query(1, 1, n, dfn[x], dfn[y]);
  return ret + s.sum;
}

// --------------
cin >> n >> q, w[n] = n;
for (int i = 1, u, v; i < n; i++) {
  cin >> u >> v, g[u].push_back(v), g[v].push_back(u), w[i] = i;
}
DFS(1, 0), DFS1(1, 0, 1), tot = n, tr.build(1, 1, n);
for (; q; q--) {
  cin >> op >> l >> r;
  if (op == 1) {
    updater(l, r);
  } else {
    cout << queryr(l, r) << '\n';
  }
}
tr.init(), tot = 0;
fill(w, w + kMaxN, 0), fill(sz, sz + kMaxN, 0), fill(son, son + kMaxN, 0), fill(f, f + kMaxN, 0);
fill(dep, dep + kMaxN, 0), fill(top, top + kMaxN, 0), fill(dfn, dfn + kMaxN, 0), fill(id, id + kMaxN, 0);
for (int i = 1; i <= n; i++) {
  g[i].clear();
}

\(\text{Prob.8}\) P6362 平面欧几里得最小生成树

算法

人类智慧,kruskal

思路

我们充分发扬人类智慧,kruskal 是按照长度从小到大贪心选择的,于是我们可以使用类似平面最近点对的思路,偏移后按照 \(x\times y\) 从小到大排序

根据数学直觉,此时长度较短的边在数组中相距不会较远,于是向左找 \(1000\) 个点,在这 \(1000n\) 条边中做 kruskal,但是这样会 T 飞

我们再一次发扬人类智慧,在数据随机的前提下,在 \(n\le 10^5\) 的最小生成树中,最长边的平方大概率不会超过 \(5\times 10^7\),所以只需要存下长度满足条件的边做 kruskal 即可

时间复杂度:\(O(\texttt{rand})\)

Code

struct P {
  LL x, y, s;
  bool operator<(const P &o) const { return s < o.s; }
} a[kMaxN], e[kMaxN << 8];

int Find(int x) { return fa[x] == x ? x : fa[x] = Find(fa[x]); }

// -------------

cin >> n;
for (int i = 1; i <= n; i++) {
  cin >> a[i].x >> a[i].y, a[i].s = (a[i].x + D) * (a[i].y + D);
}
sort(a + 1, a + n + 1), iota(fa + 1, fa + n + 1, 1ll);
for (int i = 1; i <= n; i++) {
  for (int j = max(1, i - 1000); j < i; j++) {
    tmp = (a[i].x - a[j].x) * (a[i].x - a[j].x) + (a[i].y - a[j].y) * (a[i].y - a[j].y);
    (tmp < 50000000) && (e[++tot] = (P){i, j, tmp}, 0);
  }
}
sort(e + 1, e + tot + 1), tot = 1;
for (int i = 1; tot < n; i++) {
  if (Find(e[i].x) != Find(e[i].y)) {
    fa[Find(e[i].x)] = Find(e[i].y), ans += sqrt(e[i].s), tot++;
  }
}
cout << fixed << setprecision(7) << ans << '\n';

\(\text{Prob.9}\) CF1411G No Game No Life

算法

SG 函数,高斯消元法

思路

在随机加点进行完成后,这就变成了一个简单图游戏板子,按照 SG 函数定义求就可以了

于是我们可以将原问题转化为:

  • 初始令 \(x=0\)
  • 随机选择 \(k\in[1,n+1]\),若 \(k=n+1\) 则结束游戏,若 \(v\not =0\)Alice 获胜
  • 否则令 \(x:=x \oplus sg_k\)

\(\text{Lemma}\):在本题的条件下,\(sg_i\) 异或起来的最大值 \(\le 511\)
显然若一个点的 SG 值等于 \(k\),至少需要 \(\displaystyle\sum_{i=1}^{k}=\frac{k(k-1)}{2}\) 条边
又因为总边数 \(\le 10^5\),则 \(\max\{sg_i\}\le 317\),异或后的最大值为 \(511\)

\[\text{Q.E.D.} \]

\(f_i\) 为游戏结束时 \(x=i\) 的概率,\(cnt_i\)\(sg_k=i\) 的数量

根据 SG 函数和转化后游戏的定义,我们有:

\[f_i=\frac{\displaystyle\sum_{j=0}^{511}f_jcnt_{i\oplus j}}{n+1} \]

根据 \(\text{Lemma}\),我们还有:

\[\displaystyle\sum_{i=0}^{511}f_i=1 \]

于是我们得到了一个有 \(512\) 个未知数和 \(512\) 个方程的线性方程组,用高斯消元求解即可,答案为 \(1-f_0\)

时间复杂度:\(O(n\log{n}+512^3)\)

Code

void Solve() {
  for (int i = 0; i <= N; i++) {
    for (int j = i + 1; j <= N; j++) {
      if (dp[j][i]) {
        LL k = dp[j][i] * P(dp[i][i], kP - 2) % kP;
        for (int l = i; l <= N + 1; l++) {
          dp[j][l] = (dp[j][l] - (dp[i][l] * k % kP) + kP) % kP;
        }
      }
    }
  }
  for (int i = N; ~i; i--) {
    for (int j = i + 1; j <= N; j++) {
      dp[i][N + 1] = (dp[i][N + 1] - (dp[i][j] * f[j] % kP) + kP) % kP;
    }
    f[i] = dp[i][N + 1] * P(dp[i][i], kP - 2) % kP;
  }
}
void DFS(int u) {
  if (!vis[u]) {
    for (int v : g[u]) {
      DFS(v), s[u][sg[v]] = 1;
    }
    for (; s[u][sg[u]]; sg[u]++) {
    }
    vis[u] = 1;
  }
}

// --------------

cin >> n >> m, p = P(n + 1, kP - 2);
for (int i = 1, u, v; i <= m; i++) {
  cin >> u >> v, g[u].push_back(v);
}
for (int i = 1; i <= n; i++) {
  DFS(i);
}
for (int i = 1; i <= n; i++) {
  cnt[sg[i]]++;
}
for (int i = 1; i <= N; i++) {
  dp[i][i] = 1;
  for (int j = 0; j <= N; j++) {
    dp[i][j] = (dp[i][j] - (p * cnt[i ^ j] % kP) + kP) % kP;
  }
}
for (int i = 0; i <= N + 1; i++) {
  dp[0][i] = 1;
}
Solve();
cout << (-f[0] + 1 + kP) % kP << '\n';

\(\text{Prob.10}\) CF364D Ghd

算法

随机化

思路

显然在一个集合中新增一个数,集合的 \(\gcd\) 一定不会增大。那么题目中大小至少为一半的自子集就可以转化为:大小为 \(\lceil\frac{n}{2}\rceil\) 的子集

考虑从随机化,在 \(a\) 中随机选择一个数 \(x\),将其的所有因数存入数组 \(d\) 中,将 \(d\) 排序,用 \(s_i\) 表示 \(a\) 中有因数 \(d_i\) 的数的数量

\(2s_i\ge n\),那么有因数 \(d_i\) 的数的数量 大于 \(\lceil\frac{n}{2}\rceil\)。于是可以从大到小枚举 \(s_i\),如果当前满足 \(2s_i\ge n\),那么 \(\text{ans}\) 可以直接与 \(d_i\)\(\max\),然后直接 break

然后考虑 \(s_i\) 的求法,这是一个很典的 trick。求出 \(a\) 中的每个数与 \(x\)\(\gcd\) \(g\),将 \(s_g\)\(1\)

枚举 \(i<j\),若 \(d_j\bmod d_i=0\),则将 \(s_i\) 加上 \(s_j\)。证明比较简单,第一步没什么好解释的,第二步可以理解为因为被 \(g\) 的倍数整除的数一定能被 \(g\) 整除,所以需要将 \(d_i\) 的倍数加入 \(s_i\)

随机 \(T\) 次错误的概率大约为 \(\frac{1}{2^T}\),取 \(T=10\) 时可以保证在不超时的情况下取到最大的正确率

时间复杂度:\(O(T(d(a_i)^2+n))\),其中 \(d(a_i)\)\(a_i\) 的因数个数

代码

ios::sync_with_stdio(0), cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) {
  cin >> a[i];
}
for (int T = 10; T; T--, cnt = 0) {
  LL x = a[(Rand() % n) + 1];
  for (LL i = 1; i * i <= x; i++) {
    if (x % i == 0) {
      d[++cnt] = i, i * i != x && (d[++cnt] = x / i);
    }
  }
  sort(d + 1, d + cnt + 1), fill(s, s + kMaxM, 0);
  for (int i = 1; i <= n; i++) {
    s[lower_bound(d + 1, d + cnt + 1, __gcd(x, a[i])) - d]++;
  }
  for (int i = 1; i <= cnt; i++) {
    for (int j = i + 1; j <= cnt; j++) {
      d[j] % d[i] == 0 && (s[i] += s[j]);
    }
  }
  for (int i = cnt; i; i--) {
    if (s[i] * 2 >= n) {
      ans = max(ans, d[i]);
      break;
    }
  }
}
cout << ans << '\n';

\(\text{Prob.11}\) 核桃OJ P5670 失忆序列

算法

没有爆算

思路

直接根据题意硬推式子

\(f_i=\displaystyle\sum_{j=0}^{i-1}f_j 2^{i-j}+\sum_{j=0}^{i-1}g_j\)

\(\displaystyle\sum_{j=0}^{i-1}g_j=1+\sum_{j=0}^{i-1}(4^j-2^j)=1+\sum_{j=0}^{i-1}4^j-\sum_{j=0}^{i-1}2^j=\frac{1-4^i}{1-4}-2^i+2=\frac{4^i}{3}-2^i+\frac{5}{3}\)

\(\displaystyle h_i=\frac{4^i}{3}-2^i+\frac{5}{3}\),那么 \(f_i=\displaystyle\sum_{j=0}^{i-1}2^{i-j} f_j+h_i\)

枚举 \(f\) 的前几项关于 \(h\) 的表达式:

\[f_1=h_1 \]

\[f_2=2^2f_0+2^1f_1+h_2=2^1h_1+h_2 \]

\[f_3=2^3f_0+2^2f_1+2^1f_2+h_3=2^2h_1+2^1(2^1h_1+h_2)+h_3=2^3h_1+2^1h_2+h_3 \]

\[f_4=2^4f_0+2^3f_1+2^2f_2+2^1f_3=2^3h_1+2^2(2^1h_1+h_2)+2^1(2^3h_1+2^1h_2+h_3)+h_4=2^5h_1+2^3h_2+2^1h_3+h_4 \]

观察规律得:\(\displaystyle f_i=h_i+\sum_{j=1}^{i-1}h_{i-j}2^{2j-1}\)

将得到的 \(h\) 代入式子中,得:

\[\displaystyle f_i=\frac{4^i}{3}-2^i+\frac{5}{3}\sum_{j=1}^{i-1}2^{2j-1}(\frac{4^{i-j}}{3}-2^{i-j}+\frac{5}{3}) \]

\[\displaystyle\qquad\qquad\qquad\qquad\qquad\quad\!\!\! =\frac{4^i}{3}-2^i+\frac{5}{3}+\sum_{j=1}^{i-1}(\frac{2^{2j-1}\times 2^{2i-2j}}{3}-2^{2j-1}\times 2^{i-j}+\frac{5\times 2^{2j-1}}{3}) \]

\[\displaystyle\qquad\qquad\qquad\quad =\frac{4^i}{3}-2^i+\frac{5}{3}+\sum_{j=1}^{i-1}\frac{2^{2i-1}}{3}-\sum_{j=1}^{i-1}2^{i+j-1}+\frac{5}{3}\times \sum_{j=1}^{i-1}2^{2j-1} \]

\[\displaystyle\qquad\qquad\qquad\qquad\!\! =\frac{4^i}{3}-2^i+\frac{5}{3}+\frac{(i-1)2^{2i-1}}{3}+2^i\times \sum_{j=0}^{i-2}2^j+\frac{10}{3}\times \sum_{j=1}^{i-1}4^j \]

\[\displaystyle\qquad\qquad\qquad\qquad\! =\frac{4^i}{3}-2^i+\frac{5}{3}+\frac{(i-1)2^{2i-1}}{3}-2^i(2^{i-1}-1)+\frac{10}{3}\times \frac{4^{i-1}}{3} \]

\[\displaystyle\qquad\qquad\qquad\qquad =\frac{4^i}{3}-2^i+\frac{5}{3}+\frac{(i-1)2^{2i-1}}{3}-2^{2i-1}+2^{i}+\frac{10(2^{2i-2}-1)}{9} \]

\[\displaystyle\quad\qquad\qquad\qquad\qquad\qquad\quad =\frac{6\times 2^{2i-1}+15-3i\times 3^{2i-1}-3\times 3^{2i-1}-9\times 3^{2i-1}+5\times 3^{2i-1}-10}{9} \]

\[\displaystyle\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\! =\frac{(6+3i-3-9+5)2^{2i-1}+5}{9} \]

\[\displaystyle\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\! =\frac{(3i-1)4^i+10}{18} \]

于是 \(\displaystyle f_n=\frac{10+(3n-1)4^n}{18}\)

按题意输出即可

时间复杂度:\(O(\log n)\)

代码

LL P(LL x, LL y, LL ret = 1) {
  for (; y; (y & 1) && ((ret *= x) %= kP), (x *= x) %= kP, y >>= 1) {
  }
  return ret;
}

// --------------

cin >> n, ans = ((3 * n - 1) % kP) * P(4, n) % kP;
(ans += 10) %= kP, (ans *= P(18, kP - 2)) %= kP;
cout << ans << '\n';
posted @ 2024-11-29 19:58  BluemoonQwQ  阅读(33)  评论(0)    收藏  举报