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\),那么:
这一部分可以用 \(\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 函数和转化后游戏的定义,我们有:
根据 \(\text{Lemma}\),我们还有:
于是我们得到了一个有 \(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\) 的表达式:
观察规律得:\(\displaystyle f_i=h_i+\sum_{j=1}^{i-1}h_{i-j}2^{2j-1}\)
将得到的 \(h\) 代入式子中,得:
于是 \(\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';

浙公网安备 33010602011771号