版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_39972971/article/details/83107112
【比赛链接】
【题解链接】
**【A】**Oh Those Palindromes
【思路要点】
- 一个字符串是回文串的一个必要条件是该字符串的第一个字符与最后一个字符相同。
- 因此,记字符 出现的次数为 ,一个字符串回文子串个数的上界为 。而直接将字符串的所有字符排序可以直接取到这个上界。
- 因此,排序字符串,输出即可。
- 时间复杂度 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 2e5 + 5; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } char s[MAXN]; int main() { int n; read(n); scanf("%s", s + 1); sort(s + 1, s + n + 1); printf("%s\n", s + 1); return 0; }
**【B】**Labyrinth
【思路要点】
- 假设一条路径的起始点为 ,终点为 ,记其向左走的步数为 ,向右走的步数为 ,则 ,这意味着 是一个定值,因此,一条最小化 的路径同时也分别最小化了 和 。
- 在网格图每一对上下的点间连长度为 的边,每一对左右的点间连长度为 的边,进行 ,并剔除移动次数超标的点即可。
- 时间复杂度 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 2e3 + 5; const int MAXQ = 8e6 + 5; const int MIDQ = 4e6 + 2; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } char mp[MAXN][MAXN]; int x[MAXQ], y[MAXQ], ml[MAXQ], mr[MAXQ]; int main() { int n, m; read(n), read(m); int l = MIDQ, r = MIDQ; read(x[MIDQ]), read(y[MIDQ]); read(ml[MIDQ]), read(mr[MIDQ]); for (int i = 1; i <= n; i++) scanf("%s", mp[i] + 1); mp[x[MIDQ]][y[MIDQ]] = '*'; int ans = 0; while (l <= r) { int nx = x[l], ny = y[l], tx, ty; ans++; int nml = ml[l], nmr = mr[l++]; tx = nx - 1, ty = ny; if (mp[tx][ty] == '.') { l--, x[l] = tx, y[l] = ty; ml[l] = nml, mr[l] = nmr; mp[tx][ty] = '*'; } tx = nx + 1, ty = ny; if (mp[tx][ty] == '.') { l--, x[l] = tx, y[l] = ty; ml[l] = nml, mr[l] = nmr; mp[tx][ty] = '*'; } tx = nx, ty = ny - 1; if (nml != 0 && mp[tx][ty] == '.') { r++, x[r] = tx, y[r] = ty; ml[r] = nml - 1, mr[r] = nmr; mp[tx][ty] = '*'; } tx = nx, ty = ny + 1; if (nmr != 0 && mp[tx][ty] == '.') { r++, x[r] = tx, y[r] = ty; ml[r] = nml, mr[r] = nmr - 1; mp[tx][ty] = '*'; } } printf("%d\n", ans); return 0; }
**【C】**Dwarves, Hats and Extrasensory Abilities
【思路要点】
- 不妨将黑点划分在左侧,白点划分在右侧。
- 令当前中间区间为 , ,若 是黑点,我们令中间区间为 ,否则,我们令中间区间为 。
- 上述策略可以处理 的情况。
- 选择两条不相交的直线进行上述算法,最后取中间区间中的一个点相连得到所求直线,这样就可以处理 的情况了。
- 时间复杂度 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 2e5 + 5; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } int main() { int n; read(n); int al = 0, ar = 1e9; int bl = 0, br = 1e9; for (int i = 1; i <= n; i++) { char s[15]; if (i & 1) { int mid = (al + ar) / 2; cout << 0 << ' ' << mid << endl; scanf("%s", s); if (s[0] == 'b') al = mid; else ar = mid; } else { int mid = (bl + br) / 2; cout << 1 << ' ' << mid << endl; scanf("%s", s); if (s[0] == 'b') bl = mid; else br = mid; } } cout << 0 << ' ' << (al + ar) / 2 << ' ' << 1 << ' ' << (bl + br) / 2 << endl; return 0; }
**【D】**Candies for Children
【思路要点】
- 设定阈值 。
- 若 ,我们可以枚举走一圈使用的糖果数,并简单判断其合法性。
- 若 ,我们可以枚举走的圈数 ,设 为 间 的个数, 为其他 的个数, 为 的长度,有:
、
其中
、
其中- 由于我们希望最大化 ,取 的最大值/最小值, 的最大值/最小值分别计算即可。
- 时间复杂度 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 2e5 + 5; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } ll check(ll a, ll b, ll sum, ll x, ll la, ll ra, ll lb, ll rb) { ll y = sum - a * x; if (b != 0) assert(y % b == 0), y /= b; else assert(y == 0), y = rb; if (x >= la && x <= ra && y >= lb && y <= rb) return x + y; else return -1; } int main() { ll n, k, l, r, len; read(n), read(l), read(r), read(k); if (l <= r) len = r - l + 1; else len = n - l + 1 + r; if (n <= 1e7) { for (int i = n; i >= 0; i--) { ll tmp = k % (n + i); if (tmp == 0) tmp = n + i; if (tmp >= len && tmp >= 2 * len - (n - i + 1) && tmp <= 2 * len && tmp <= len + i) { writeln(i); return 0; } } printf("-1\n"); } else { ll ans = -1; for (int i = 0; i * n <= k; i++) { ll sum = k - (i + 1) * len - i * (n - len), tmp; if (i != 0) { tmp = sum % i; chkmax(ans, check(i + 1, i, sum, tmp, 0, len, 0, n - len)); chkmax(ans, check(i + 1, i, sum, tmp + (len - tmp) / i * i, 0, len, 0, n - len)); } tmp = (-sum % (i + 1) + (i + 1)) % (i + 1); chkmax(ans, check(i, i + 1, sum, tmp, 0, n - len, 0, len)); chkmax(ans, check(i, i + 1, sum, tmp + (n - len - tmp) / (i + 1) * (i + 1), 0, n - len, 0, len)); sum++; if (i != 0) { tmp = sum % i; chkmax(ans, check(i + 1, i, sum, tmp + (tmp != 0) * i, 1, len, 0, n - len)); chkmax(ans, check(i + 1, i, sum, tmp + (len - tmp) / i * i, 1, len, 0, n - len)); } tmp = (-sum % (i + 1) + (i + 1)) % (i + 1); chkmax(ans, check(i, i + 1, sum, tmp, 0, n - len, 1, len)); chkmax(ans, check(i, i + 1, sum, tmp + (n - len - tmp) / (i + 1) * (i + 1), 0, n - len, 1, len)); } writeln(ans); } return 0; }
**【E】**Lasers and Mirrors
【思路要点】
- 首先,若 ,则不用放置任何镜子,答案为 。
- 一旦放置任何一个镜子,答案至多为 ,我们试图构造一组答案为 的解。
- 我们不妨放弃某一束光线 ,这样,我们可以用一行将另一束光线引导到 所在的列。单纯地使用这样的方式将数组排序,使用的行数为 ,其中 表示读入的置换中环的个数。
- 考虑优化使用的行数,上述算法的弊端在于处理每一个不包含 的置换环时需要额外付出一行的代价来将 并入置换环中。我们放弃光线 ,这样每当我们要将 并入置换环中时, 光线 均在 号位置上,我们就可以将并入置换环的操作与第一次交换操作在一行内完成。使用的行数降为 。
- 时间复杂度 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 1e3 + 5; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } int n, a[MAXN]; char ans[MAXN][MAXN]; int main() { read(n); bool all = true; for (int i = 1; i <= n; i++) { read(a[i]); if (a[i] != i) all = false; for (int j = 1; j <= n; j++) ans[i][j] = '.'; } if (all) { printf("%d\n", n); for (int i = 1; i <= n; i++) printf("%s\n", ans[i] + 1); return 0; } int token = 1, pos = 0; for (int i = 1; i <= n; i++) if (a[i] == 1) pos = i; for (int i = n; i >= 1; i--) { if (token == pos) { int dest = 0; for (int j = 1; j <= n; j++) if (a[j] < j) dest = j; if (dest == 0) continue; ans[i][dest] = ans[i][a[dest]] = ans[i][pos] = '\\'; int x = dest, y = a[dest]; swap(a[x], a[y]), swap(a[pos], a[x]), pos = x; } else { int dest = 0; for (int j = 1; j <= n; j++) if (a[j] == pos) dest = j; if (dest > pos) ans[i][dest] = ans[i][pos] = '\\'; else ans[i][dest] = ans[i][pos] = '/'; swap(a[pos], a[dest]), pos = dest; } } printf("%d\n", n - 1); for (int i = 1; i <= n; i++) printf("%s\n", ans[i] + 1); return 0; }
**【F】**String Journey
【思路要点】
- 首先,我们不妨认为选取的从右到左第 个子串长度为 ,显然任何方案都可以被转化为满足上述条件的方案。
- 若从 开始向后能够选择 个子串,那么一定可以选择 个子串。令 表示从 开始向后能够至多能够选择的子串个数,直接二分每个位置的 值,用后缀数组判断是否可行,时间复杂度 。
- 注意到 ,因此 ,因此从大到小直接暴力判断每一个取值是否合法,总判断次数为 。具体判断时可以在后缀数组上二分出 足够长的区间,用线段树维护区间 值最大值。
- 时间复杂度 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 5e5 + 5; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } struct SegmentTree { struct Node { int lc, rc, Max; } a[MAXN * 2]; int n, size, root; void update(int root) { a[root].Max = max(a[a[root].lc].Max, a[a[root].rc].Max); } void build(int &root, int l, int r) { root = ++size; if (l == r) { a[root].Max = 0; return; } int mid = (l + r) / 2; build(a[root].lc, l, mid); build(a[root].rc, mid + 1, r); update(root); } void init(int x) { n = x; root = size = 0; build(root, 1, n); } void modify(int root, int l, int r, int pos, int d) { if (l == r) { a[root].Max = d; return; } int mid = (l + r) / 2; if (mid >= pos) modify(a[root].lc, l, mid, pos, d); else modify(a[root].rc, mid + 1, r, pos, d); update(root); } void modify(int pos, int d) { modify(root, 1, n, pos, d); } int query(int root, int l, int r, int ql, int qr) { if (l == ql && r == qr) return a[root].Max; int mid = (l + r) / 2, ans = 0; if (mid >= ql) chkmax(ans, query(a[root].lc, l, mid, ql, min(mid, qr))); if (mid + 1 <= qr) chkmax(ans, query(a[root].rc, mid + 1, r, max(mid + 1, ql), qr)); return ans; } int query(int l, int r) { if (l > r) return 0; else return query(root, 1, n, l, r); } } ST; namespace SuffixArray { const int MAXN = 5e5 + 5; const int MAXLOG = 22; const int MAXC = 256; int sa[MAXN], rnk[MAXN], height[MAXN]; int Min[MAXN][MAXLOG], bit[MAXN], N; void init(char *a, int n) { N = n; static int x[MAXN], y[MAXN], cnt[MAXN], rk[MAXN]; memset(cnt, 0, sizeof(cnt)); for (int i = 1; i <= n; i++) cnt[(int) a[i]]++; for (int i = 1; i <= MAXC; i++) cnt[i] += cnt[i - 1]; for (int i = n; i >= 1; i--) sa[cnt[(int) a[i]]--] = i; rnk[sa[1]] = 1; for (int i = 2; i <= n; i++) rnk[sa[i]] = rnk[sa[i - 1]] + (a[sa[i]] != a[sa[i - 1]]); for (int k = 1; rnk[sa[n]] != n; k <<= 1) { for (int i = 1; i <= n; i++) { x[i] = rnk[i]; y[i] = (i + k <= n) ? rnk[i + k] : 0; } memset(cnt, 0, sizeof(cnt)); for (int i = 1; i <= n; i++) cnt[y[i]]++; for (int i = 1; i <= n; i++) cnt[i] += cnt[i - 1]; for (int i = n; i >= 1; i--) rk[cnt[y[i]]--] = i; memset(cnt, 0, sizeof(cnt)); for (int i = 1; i <= n; i++) cnt[x[i]]++; for (int i = 1; i <= n; i++) cnt[i] += cnt[i - 1]; for (int i = n; i >= 1; i--) sa[cnt[x[rk[i]]]--] = rk[i]; rnk[sa[1]] = 1; for (int i = 2; i <= n; i++) rnk[sa[i]] = rnk[sa[i - 1]] + (x[sa[i]] != x[sa[i - 1]] || y[sa[i]] != y[sa[i - 1]]); } int now = 0; for (int i = 1; i <= n; i++) { if (now) now--; while (a[i + now] == a[sa[rnk[i] + 1] + now]) now++; height[rnk[i]] = now; } for (int i = 1; i <= n; i++) Min[i][0] = height[i]; for (int p = 1; p < MAXLOG; p++) { int tmp = 1 << (p - 1); for (int i = 1, j = tmp + 1; j <= n; i++, j++) Min[i][p] = min(Min[i][p - 1], Min[i + tmp][p - 1]); } for (int i = 1; i <= n; i++) { bit[i] = bit[i - 1]; if (i >= 1 << (bit[i] + 1)) bit[i]++; } } int lcp(int x, int y) { if (x == y) return N - sa[x] + 1; if (x > y) swap(x, y); int tmp = bit[y - x]; return min(Min[x][tmp], Min[y - (1 << tmp)][tmp]); } int dp[MAXN]; bool feasiable(int pos, int len) { if (len == 1) return true; len--; int l = 1, r = rnk[pos]; while (l < r) { int mid = (l + r) / 2; if (lcp(mid, rnk[pos]) >= len) r = mid; else l = mid + 1; } int ql = l; l = rnk[pos], r = N; while (l < r) { int mid = (l + r + 1) / 2; if (lcp(mid, rnk[pos]) >= len) l = mid; else r = mid - 1; } int qr = l; if (ST.query(ql, qr) >= len) return true; pos++; l = 1, r = rnk[pos]; while (l < r) { int mid = (l + r) / 2; if (lcp(mid, rnk[pos]) >= len) r = mid; else l = mid + 1; } ql = l; l = rnk[pos], r = N; while (l < r) { int mid = (l + r + 1) / 2; if (lcp(mid, rnk[pos]) >= len) l = mid; else r = mid - 1; } qr = l; return ST.query(ql, qr) >= len; } void work(int n) { int ans = 0, now = 1, pos = n + 1; for (int i = n; i >= 1; i--) { while (!feasiable(i, now)) { now--, pos--; ST.modify(rnk[pos], dp[pos]); } dp[i] = now; chkmax(ans, now); now = now + 1; } writeln(ans); } } int n; char s[MAXN]; int main() { read(n), ST.init(n); scanf("\n%s", s + 1); SuffixArray :: init(s, n); SuffixArray :: work(n); return 0; }