比赛地址:THP3 考前信心赛。php
感谢原出题人的贡献:第一题 CF1422C,第四题 CF1422D。html
全部题目背景均出自 《秘封俱乐部》系列专辑附带故事,感谢太田顺也先生的创造。git
感谢 Kersen、xwmwr、Ypay、Rainycolor_Mahou 的鼎力相助。算法
超级亲民的一场,大片部分分,没有任何高级算法技巧,有一车大样例。数组
给定一长度为 \(n\) 的只由 \(0\sim 9\) 构成的字符串,求删除一个任意非空子串后获得的全部十进制数 的和。
答案对 \(10^9 + 7\) 取模。
\(1\le n\le 2 \times 10^6\)。
1S,128MB。函数
定义 \(f(l,r)\) 表示子串 \([l,r]\) 组成的十进制数。
考虑枚举删除的子串的最后一位 \(x\),获得的十进制数的和为:工具
预处理出后缀表示的十进制数,枚举 \(x\) 的时候维护出前缀十进制数的和便可。
预处理 \(10^?\) 后时间复杂度 \(O(n)\)。优化
//知识点:瞎搞 /* By:Luckyblock */ #include <cctype> #include <cstdio> #include <cstring> #include <algorithm> #define LL long long const int kN = 2e6 + 10; const LL mod = 1e9 + 7; //============================================================= int n; char s[kN]; LL ans, sum, pow10[kN], suf[kN]; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); return f * w; } //============================================================= int main() { scanf("%s", s + 1); n = strlen(s + 1); pow10[0] = 1; for (int i = 1; i <= n; ++ i) { pow10[i] = pow10[i - 1] * 10ll % mod;; } for (int i = n; i >= 1; -- i) { suf[i] = suf[i + 1]; suf[i] += (s[i] - '0') * pow10[n - i] % mod; suf[i] %= mod; } LL val = 0; for (int i = 1; i <= n; ++ i) { ans += sum * pow10[n - i] % mod + (i - 1) * suf[i] % mod; ans %= mod; val = (10ll * val % mod + s[i] - '0') % mod; sum = (sum + val) % mod; } printf("%lld\n", ans); return 0; }
定义:spa
\[f(n) = \sum_{i=1}^{n}\gcd(i,n) \]有 \(m\) 次询问,每次询问给定参数 \(l,r\),求:code
\[\sum_{i=l}^{r}f(i)\pmod {998244353} \]\(1\le m\le 10^6\),\(1\le l \le r\le 10^6\)。
1S,128MB。
这个式子是 Luckyblock
作 P5518 [MtOI2019]幽灵乐团 的时候化出来的,由于比较基础,因此就拿过来用了。
考虑化一下 \(f\)。
考虑对于每个 \(1\sim n\) 的值,能做为多少数对的 \(\gcd\),因而有:
发现 \(\gcd(i,n) = d\) 的必要条件是 \(d|n\),原式能够改成:
考虑什么样的 \(i\),知足 \(\gcd(i,n) = d\),显然当且仅当 \(i=kd(k\in \mathbb{N^*})\),且 \(\gcd(k,\frac{n}{d})=1\) 时知足条件。为保证 \(i\le n\),有 \(k \le \left\lfloor\frac{n}{d}\right\rfloor\)。
因而考虑把 \(d\) 提出来,改成枚举上述的 \(k\),原式等于:
考虑后面一个 \(\sum\) 的实际含义,表示 \(1\sim \frac{n}{d}\) 中与 \(\frac{n}{d}\) 互质的数的个数,符合欧拉函数的定义,因而原式等于:
线性筛预处理 \(\varphi\) 后,用埃氏筛便可筛出 \(1\sim 10^6\) 的全部 \(f\)。
作个前缀和便可回答区间询问。
复杂度 \(O(n\log n + m)\)。
//知识点:数论 /* By:Luckyblock */ #include <algorithm> #include <cctype> #include <cstdio> #include <cstring> #define LL long long const int kN = 1e6 + 10; const int kMax = 1e6; const int mod = 998244353; //============================================================= int p_num, p[kN], phi[kN]; int f[kN], sum[kN]; bool vis[kN]; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) { w = (w << 3) + (w << 1) + (ch ^ '0'); } return f * w; } void Chkmax(int &fir_, int sec_) { if (sec_ > fir_) fir_ = sec_; } void Chkmin(int &fir_, int sec_) { if (sec_ < fir_) fir_ = sec_; } void Init() { phi[1] = 1; for (int i = 2; i <= kMax; ++ i) { if (! vis[i]) { p[++ p_num] = i; phi[i] = i - 1; } for (int j = 1; j <= p_num && i * p[j] <= kMax; ++ j) { vis[i * p[j]] = true; if (i % p[j] == 0) { phi[i * p[j]] = phi[i] * p[j]; break; } phi[i * p[j]] = phi[i] * (p[j] - 1); } } for (int i = 1; i <= kMax; ++ i) { for (int j = i; j <= kMax; j += i) { f[j] += 1ll * phi[i] * (j / i) % mod; f[j] %= mod; } } for (int i = 1; i <= kMax; ++ i) { sum[i] = (sum[i - 1] + f[i]) % mod; } } //============================================================= int main() { Init(); int m = read(); while (m --) { int l = read(), r = read(); printf("%d\n", (sum[r] - sum[l - 1] + mod) % mod); } return 0; }
给定两周长为 \(n\) 的 \(01\) 环。
将它们叠放在一块儿,能够随意旋转两个环。
定义一种放置方案的价值为两个环对应位置的与的和,求最大价值。
\(1\le n\le 5\times 10^4\)。
1S,128MB。
这题是今年 10 月份去刷题班的晚上偷着听 mp3 的时候想出来的。
那个 mp3 就如题面所说,能够经过旋转得到不一样的音量大小,实在是太厉害了= =
先把两个环展开,固定其中一个环,枚举另外一个环的最多 \(n\) 种形态。
使用 bool
数组记录形态,暴力与起来求答案,复杂度 \(O(n^2)\)。
发现若是 \(n\) 较小的话,能够直接用整形变量存下展开后的环。
修改环的形态,能够在前一个形态的基础上经过位运算简单获得。
取与操做的复杂度也变得很小,总复杂度仅为 \(O(n)\) 级别。
考虑把一长度为 \(n\) 的 bool
数组压成一长度为 \(\frac{n}{64}\) 的 unsigned long long
数组。
形态变化能够经过右移和赋值完成,取与时对每个数分别取与,并求 \(1\) 的个数。
总复杂度 \(O\left(\dfrac{n^2}{64}\right)\)。
C++ 中提供了一个与上述过程实现相似的容器,叫作 bitset
。
能够将 bitset
当作一个支持取交并的 bool
数组使用。
须要注意的是 bitset
的时空复杂度是与系统位数有关的。
是一个很是简单的小工具,详细请看:OI-Wiki。
如下是用 bitset
实现的代码:
// /* By:Luckyblock */ #include <algorithm> #include <cctype> #include <cstdio> #include <cstring> #include <bitset> #define LL long long const int kMaxn = 1e5 + 10; //============================================================= int n, ans; char sa[kMaxn], sb[kMaxn]; std::bitset <kMaxn> a, b, c; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); return f * w; } void Chkmax(int &fir_, int sec_) { if (sec_ > fir_) fir_ = sec_; } void Chkmin(int &fir_, int sec_) { if (sec_ < fir_) fir_ = sec_; } //============================================================= int main() { n = read(); scanf("%s", sa + 1); scanf("%s", sb + 1); for (int i = 1; i <= n; ++ i) { a[i] = (sa[i] == '1'); b[i] = (sb[i] == '1'); } for (int i = 1; i <= n; ++ i) { b[n + 1] = b[1]; b >>= 1; Chkmax(ans, (a & b).count()); } printf("%d\n", ans); return 0; }
给定一 \(n\times n\) 的网格,图中有 \(m\) 个给定的关键点。
给定人的起点终点,每次能够向上下左右任意方向移动一格。
特别地,当人与一个关键点横坐标相同或纵坐标相同时,能够瞬移到关键点,不花费次数。
求从起点到终点的最小移动次数。
\(1\le n\le 10^9\),\(1\le m\le 10^5\)。
1S,256MB。
有个显然的暴力,每一个点向上下左右的点连权值为 1 的双向边。每一个关键点向同行同列的点连权值为 1 的双向边。而后跑 Dijkstra。
点数边数是 \(O(n^2)\) 级别的,时间复杂度 \(O(n^2\log (n^2))\) 级别,指望得分 30pts。
注意特判一下 task1。
//知识点:建图,最短路 /* By:Luckyblock */ #include <algorithm> #include <cctype> #include <cstdio> #include <cstring> #include <queue> #define pr std::pair #define mp std::make_pair #define LL long long const int kM = 1e6 + 10; const int kE = 6e6 + 10; //============================================================= int n, m, sx, sy, tx, ty; int e_num, head[kM], v[kE], w[kE], ne[kE]; LL dis[kM]; bool vis[kM]; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) { w = (w << 3) + (w << 1) + (ch ^ '0'); } return f * w; } void Chkmax(int &fir_, int sec_) { if (sec_ > fir_) fir_ = sec_; } void Chkmin(int &fir_, int sec_) { if (sec_ < fir_) fir_ = sec_; } void AddEdge(int u_, int v_, int w_) { v[++ e_num] = v_; w[e_num] = w_; ne[e_num] = head[u_]; head[u_] = e_num; } void Dijkstra(int s_) { std::priority_queue <pr <LL, int> > q; memset(dis, 63, sizeof (dis)); memset(vis, 0, sizeof (vis)); dis[s_] = 0; q.push(mp(0, s_)); while (! q.empty()) { int u_ = q.top().second; q.pop(); if (vis[u_]) continue ; vis[u_] = true; for (int i = head[u_]; i; i = ne[i]) { int v_ = v[i], w_ = w[i]; if (dis[u_] + w_ < dis[v_]) { dis[v_] = dis[u_] + w_; q.push(mp(-dis[v_], v_)); } } } } int Id(int x_, int y_) { return (x_ - 1) * n + y_; } //============================================================= int main() { n = read(), m = read(); sx = read(), sy = read(); tx = read(), ty = read(); if (m == 0) { printf("%d\n", abs(tx - sx) + abs(ty - sy)); return 0; } for (int i = 1; i <= n; ++ i) { for (int j = 1; j <= n; ++ j) { if (i + 1 <= n) AddEdge(Id(i, j), Id(i + 1, j), 1); if (j + 1 <= n) AddEdge(Id(i, j), Id(i, j + 1), 1); if (i - 1 > 0) AddEdge(Id(i, j), Id(i - 1, j), 1); if (j - 1 > 0) AddEdge(Id(i, j), Id(i, j - 1), 1); } } for (int i = 1; i <= m; ++ i) { int x = read(), y = read(); for (int j = 1; j <= n; ++ j) { AddEdge(Id(x, j), Id(x, y), 0); AddEdge(Id(j, y), Id(x, y), 0); } } Dijkstra(Id(sx, sy)); printf("%lld\n", dis[Id(tx, ty)]); return 0; }
\(n\) 这么大,显然标算是个与 \(n\) 无关的算法。
考虑从起点到终点的最短路径。
若不通过任何一个关键点,最短路即为两点曼哈顿距离,能够直接算出。
不然能够把最短路当作:起点 \(\rightarrow\) 关键点 \(\rightarrow\) 终点。
因而将关键点做为中继点,改变连边方式:
再跑 Dijkstra,点数边数变为 \(O(m^2)\) 级别,时间复杂度 \(O(m^2 \log (m^2))\) 级别,指望得分 70pts。
//知识点:建图,最短路 /* By:Luckyblock */ #include <algorithm> #include <cctype> #include <cstdio> #include <cstring> #include <queue> #define pr std::pair #define mp std::make_pair #define LL long long const int kM = 1e5 + 10; const int kE = 6e6 + 10; //============================================================= struct Node { int x, y; } a[kM]; int n, m, sx, sy, tx, ty; int e_num, head[kM], v[kE], w[kE], ne[kE]; LL dis[kM]; bool vis[kM]; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) { w = (w << 3) + (w << 1) + (ch ^ '0'); } return f * w; } void Chkmax(int &fir_, int sec_) { if (sec_ > fir_) fir_ = sec_; } void Chkmin(int &fir_, int sec_) { if (sec_ < fir_) fir_ = sec_; } void AddEdge(int u_, int v_, int w_) { v[++ e_num] = v_; w[e_num] = w_; ne[e_num] = head[u_]; head[u_] = e_num; } void Dijkstra(int s_) { std::priority_queue <pr <LL, int> > q; memset(dis, 63, sizeof (dis)); memset(vis, 0, sizeof (vis)); dis[s_] = 0; q.push(mp(0, s_)); while (! q.empty()) { int u_ = q.top().second; q.pop(); if (vis[u_]) continue ; vis[u_] = true; for (int i = head[u_]; i; i = ne[i]) { int v_ = v[i], w_ = w[i]; if (dis[u_] + w_ < dis[v_]) { dis[v_] = dis[u_] + w_; q.push(mp(-dis[v_], v_)); } } } } //============================================================= int main() { n = read(), m = read(); sx = read(), sy = read(); tx = read(), ty = read(); AddEdge(0, m + 1, abs(tx - sx) + abs(ty - sy)); for (int i = 1; i <= m; ++ i) { int x = read(), y = read(); a[i] = (Node) {x, y}; AddEdge(0, i, std::min(abs(sx - x), abs(sy - y))); AddEdge(i, m + 1, abs(tx - x) + abs(ty - y)); } for (int i = 1; i <= m; ++ i) { for (int j = i + 1; j <= m; ++ j) { AddEdge(i, j, std::min(abs(a[i].x - a[j].x), abs(a[i].y - a[j].y))); AddEdge(j, i, std::min(abs(a[i].x - a[j].x), abs(a[i].y - a[j].y))); } } Dijkstra(0); printf("%lld\n", dis[m + 1]); return 0; }
为表达方便,如下钦定两关键点间的距离为 \(\min(|x_1-x_2|, |y_1-y_2|)\)。
考虑三个关键点之间的连边,若是出现下图状况:
显然 \(A\rightarrow C\) 的距离不小于 \(A\rightarrow B\) 与 \(B\rightarrow C\) 的距离之和。
所以能够不连 \(A\rightarrow C\) 的边,不会影响 \(A\rightarrow C\) 的最短路,能够删除这条边。
再考虑更通常的状况,若是有下图:
\(A\rightarrow C\) 的距离仍然不小于 \(A\rightarrow B\) 与 \(B\rightarrow C\) 的距离之和。
所以能够不连 \(A\rightarrow C\) 的边,不会影响 \(A\rightarrow C\) 的最短路。
但注意到 \(A\rightarrow C\) 的边会对 \(A\rightarrow B\) 的最短路做出贡献,这条边不能删除。
因而获得一个对算法二的优化:
先把关键点按 \(x\) 坐标排序,在排序后相邻两个点连 双向边。再把关键点按 \(y\) 坐标排序,在排序后相邻两点连 双向边。
跑出来的最短路与以前的相等,但点数边数仅为 \(O(m)\) 级别,时间复杂度 \(O(m\log m)\) 级别,能够经过。
注意空间大小。
//知识点:建图,最短路 /* By:Luckyblock */ #include <algorithm> #include <cctype> #include <cstdio> #include <cstring> #include <queue> #define pr std::pair #define mp std::make_pair #define LL long long const int kM = 1e5 + 10; const int kE = 6e5 + 10; //============================================================= struct Node { int x, y, id; } a[kM]; int n, m, sx, sy, tx, ty; int e_num, head[kM], v[kE], w[kE], ne[kE]; LL dis[kM]; bool vis[kM]; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) { w = (w << 3) + (w << 1) + (ch ^ '0'); } return f * w; } void Chkmax(int &fir_, int sec_) { if (sec_ > fir_) fir_ = sec_; } void Chkmin(int &fir_, int sec_) { if (sec_ < fir_) fir_ = sec_; } bool CMPx(Node fir_, Node sec_) { return fir_.x < sec_.x; } bool CMPy(Node fir_, Node sec_) { return fir_.y < sec_.y; } void AddEdge(int u_, int v_, int w_) { v[++ e_num] = v_; w[e_num] = w_; ne[e_num] = head[u_]; head[u_] = e_num; } void Dijkstra(int s_) { std::priority_queue <pr <LL, int> > q; memset(dis, 63, sizeof (dis)); memset(vis, 0, sizeof (vis)); dis[s_] = 0; q.push(mp(0, s_)); while (! q.empty()) { int u_ = q.top().second; q.pop(); if (vis[u_]) continue ; vis[u_] = true; for (int i = head[u_]; i; i = ne[i]) { int v_ = v[i], w_ = w[i]; if (dis[u_] + w_ < dis[v_]) { dis[v_] = dis[u_] + w_; q.push(mp(-dis[v_], v_)); } } } } //============================================================= int main() { n = read(), m = read(); sx = read(), sy = read(); tx = read(), ty = read(); AddEdge(0, m + 1, abs(tx - sx) + abs(ty - sy)); for (int i = 1; i <= m; ++ i) { int x = read(), y = read(); a[i] = (Node) {x, y, i}; AddEdge(0, i, std::min(abs(sx - x), abs(sy - y))); AddEdge(i, m + 1, abs(tx - x) + abs(ty - y)); } std::sort(a + 1, a + m + 1, CMPx); for (int i = 2; i <= m; ++ i) { LL val = std::min(abs(a[i].x - a[i - 1].x), abs(a[i].y - a[i - 1].y)); AddEdge(a[i - 1].id, a[i].id, val); AddEdge(a[i].id, a[i - 1].id, val); } std::sort(a + 1, a + m + 1, CMPy); for (int i = 2; i <= m; ++ i) { LL val = std::min(abs(a[i].x - a[i - 1].x), abs(a[i].y - a[i - 1].y)); AddEdge(a[i - 1].id, a[i].id, val); AddEdge(a[i].id, a[i - 1].id, val); } Dijkstra(0); printf("%lld\n", dis[m + 1]); return 0; }