题目传送门
官方题解传送门
-------------------这次的题目出的不错,有9题是我赛后能做出来的。但是数据太智障了,重配好几次还是有问题。
A .斑羚飞渡
sol:贪心:如果x[i] + y[i] < m,则第i只斑羚一定到不了对岸,所以要尽量多的使用这种斑羚当跳板;如果x[i] + y[i] >= m,则两只这样的斑羚一定有一只可以借助另一只到达对岸;
ps:比赛的时候数据出锅了,重配之后还是有锅,还好比赛的时候没花时间在这题上面。
- 贪心
#include "bits/stdc++.h" using namespace std; vector<int> v1, v2; int n, m, ans; int main() { int x, y; scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) { scanf("%d%d", &x, &y); if (x >= m) { ans++; continue; } if (x + y >= m) v1.push_back(y); else v2.push_back(x); } sort(v1.begin(), v1.end()); sort(v2.begin(), v2.end()); for (int i = 0; i < v2.size(); i++) { if (v2[i] + v1.back() >= m) { v1.pop_back(); ans++; } } printf("%d ", ans + v1.size() / 2); return 0; }
B .诡异的因数
sol:相当暴力。t * n都能过,优化一下就是t * sqrt(n);
- t * sqrt(n)的暴力
#include "bits/stdc++.h" using namespace std; int main() { int t, n; scanf("%d", &t); while (t--) { scanf("%d", &n); int cnt = 0; for (int i = 1; i * i <= n; i++) { if (n % i == 0) { cnt++; if (n / i != i) cnt++; } } printf("%d ", cnt); } return 0; }
sol:觉得还有更优的打表方法,但是没想到。去代码区看了一下效率最高的代码果然是打表。值得学习;
- 打表预处理
#include "bits/stdc++.h" using namespace std; const int MAXN = 1e4 + 5; int cnt[MAXN]; void init() { for (int i = 1; i <= 10000; i++) { if (i * i <= 10000) cnt[i * i]++; for (int j = i + 1; i * j <= 10000; j++) cnt[i * j] += 2; } } int main() { init(); int t, n; scanf("%d", &t); while (t--) { scanf("%d", &n); printf("%d ", cnt[n]); } return 0; }
C .表单
sol:用一个set记录已经出现的串,一个cnt记录上一次去重之后出现了几个重复串。
ps:我被这题坑到了,原先我一直用C语言风格的输入输出。但是scanf不方便输入string类型。所以就采用了cin,后来得知这题的整型用cin输入会wa。不知道是什么数据造成的。而且数据里还有空行之前还没说明,而且op还有非1非2的情况。
- 事先记录
#include "bits/stdc++.h" using namespace std; string s; set<string> st; int main() { int n, q, op, cnt = 0; scanf("%d%d", &n, &q); while (n--) { cin >> s; if (st.count(s)) cnt++; else st.insert(s); } while (q--) { scanf("%d", &op); if (op == 1) { cin >> s; if (st.count(s)) cnt++; else st.insert(s); } else { printf("%d ", cnt); cnt = 0; } } return 0; }
D .分数的运算
sol:加减通分,乘除直接算。减法是加法逆运算,除法是乘法逆运算。写俩自定义函数就完事。
- 数学
#include "bits/stdc++.h" using namespace std; typedef long long LL; void add(LL a, LL b, LL c, LL d) { LL g = __gcd(b, d); LL x = a * d / g + c * b / g, y = b * d / g; if (y < 0) x = -x, y = -y; if (x == 0) { puts("0 0"); return; } g = __gcd(x, y); printf("%lld %lld ", x / g, y / g); } void mul(LL a, LL b, LL c, LL d) { LL x = a * c, y = b * d; if (x == 0) { puts("0 0"); return; } LL g = __gcd(x, y); printf("%lld %lld ", x / g, y / g); } int main() { LL a, b, c, d; scanf("%lld%lld%lld%lld", &a, &b, &c, &d); add(a, b, c, d); add(a, b, -c, d); mul(a, b, c, d); mul(a, b, d, c); return 0; }
E .希望
sol:就是一个01背包,但是每个物品的最小费用不明确。要解决这个问题,可以用线段树;
- 线段树+01背包
#include "bits/stdc++.h" using namespace std; typedef long long LL; const int MAXN = 1e5 + 5; const int INF = 0x3f3f3f3f; struct Node { int l, r; int _min; } segTree[MAXN * 4]; int a[MAXN], b[MAXN], c[MAXN]; int dp[MAXN]; void build(int p, int l, int r) { segTree[p].l = l; segTree[p].r = r; segTree[p]._min = INF; if (l != r) { int mid = l + r >> 1; build(p << 1, l, mid); build(p << 1 | 1, mid + 1, r); } } void update(int p, int l, int r, int x) { if (segTree[p].l >= l && segTree[p].r <= r) { segTree[p]._min = min(segTree[p]._min, x); return; } int mid = segTree[p].l + segTree[p].r >> 1; if (l <= mid) update(p << 1, l, r, x); if (r > mid) update(p << 1 | 1, l, r, x); } int query(int p, int i) { if (segTree[p].l == segTree[p].r) return segTree[p]._min; int mid = segTree[p].l + segTree[p].r >> 1; if (i <= mid) return min(query(p << 1, i), segTree[p]._min); else return min(query(p << 1 | 1, i), segTree[p]._min); } int main() { int n, k, m, l, r, x; scanf("%d%d%d", &n, &k, &m); for (int i = 1; i <= n; i++) scanf("%d", &a[i]); build(1, 1, n); for (int i = 1; i <= m; i++) { scanf("%d%d%d", &l, &r, &x); update(1, l, r, x); } LL ans = 0, tot = 0; for (int i = 1; i <= n; i++) { ans += a[i]; if (a[i] < 0) { b[++tot] = -a[i]; c[tot] = query(1, i); } } for (int i = 1; i <= tot; i++) { for (int j = k; j >= c[i]; j--) { dp[j] = max(dp[j], dp[j - c[i]] + b[i]); } } printf("%lld ", ans + dp[k]); return 0; }
线段树里的_min有点像懒标记,但是不用向下扩展。
sol:线段树的做法是从别人那看来的,看完发现其实我可以用并查集来代替线段树的功能,而且更高效;
- 并查集+01背包
#include "bits/stdc++.h" using namespace std; typedef long long LL; const int MAXN = 1e5 + 5; const int INF = 0x3f3f3f3f; struct Node { int l, r, x; } p[MAXN]; int a[MAXN], b[MAXN], c[MAXN]; int pre[MAXN], _min[MAXN], dp[MAXN]; bool cmp(Node a, Node b) { return a.x < b.x; } int find(int k) { if (pre[k] == k) return k; return pre[k] = find(pre[k]); } int main() { int n, k, m; scanf("%d%d%d", &n, &k, &m); for (int i = 1; i <= n; i++) scanf("%d", &a[i]); for (int i = 1; i <= m; i++) scanf("%d%d%d", &p[i].l, &p[i].r, &p[i].x); sort(p + 1, p + 1 + m, cmp); for (int i = 1; i <= n; i++) pre[i] = i, _min[i] = INF; for (int i = 1; i <= m; i++) { for (int j = find(p[i].r); j >= p[i].l; j = find(j)) { _min[j] = p[i].x; pre[j] = find(j - 1); } } LL ans = 0, tot = 0; for (int i = 1; i <= n; i++) { ans += a[i]; if (a[i] < 0) { b[++tot] = -a[i]; c[tot] = _min[i]; } } for (int i = 1; i <= tot; i++) { for (int j = k; j >= c[i]; j--) { dp[j] = max(dp[j], dp[j - c[i]] + b[i]); } } printf("%lld ", ans + dp[k]); return 0; }
和hdu1698一样,这种题目也做了好几道了,每次都用线段树解一遍,并查集解一遍。01背包就是很基础的东西了。
F .跳跃的旋律
sol:用离线分块,之前没怎么看过分块,算是了解一下吧。代码很好理解
ps:这题的数据真垃圾,一开始说是暴力都能AC。然后出题人重配了数据之后号称卡飞暴力,然后就把正解也卡了。我一样的代码交上去一回儿通过一回儿超时的。而且这题现在的数据不开快读怕是过不去了吧。
- 分块
#include "bits/stdc++.h" using namespace std; const int MAXN = 500010; const int MOD = 20180718; int a[MAXN], dp[MAXN], ans[MAXN]; struct Node { int x, y, id; } q[MAXN]; bool cmp(Node a, Node b) { return a.y < b.y; } inline int read() { int n = 0; char c = getchar(); while (c < '0' || c > '9') c = getchar(); while (c >= '0' && c <= '9') { n = n * 10 + (c ^ '0'); c = getchar(); } return n; } int main() { int n = read(), m = read(), tot = 0, x, y; for (int i = 1; i <= n; i++) a[i] = read(); for (int i = 1; i <= m; i++) { x = read(), y = read(); if (1LL * y * y >= n) { int tmp = 1, j = x; while (j <= n) { tmp = 1LL * tmp * a[j] % MOD; j += y; } ans[i] = tmp; } else { ++tot; q[tot].x = x, q[tot].y = y; q[tot].id = i; } } sort(q + 1, q + 1 + tot, cmp); int d = 0; for (int i = 1; i <= tot; i++) { if (q[i].y == d) { ans[q[i].id] = dp[q[i].x]; continue; } d = q[i].y; for (int j = n; j >= 1; j--) { if (j + d > n) dp[j] = a[j]; else dp[j] = 1LL * a[j] * dp[j + d] % MOD; } ans[q[i].id] = dp[q[i].x]; } for (int i = 1; i <= m; i++) printf("%d ", ans[i]); return 0; }
G .FTOS的测试
sol:既然没一个人做出来那我就跳过了吧,连出题人都懒得敲的代码(好奇没有标程数据是怎么来的);
H .数据结构
sol:我是用分块,块内排序二分找相同。这是最好想最好敲的方案了;
ps:这题还考细心,x的范围到10W。而且l可能比r大,比赛结束都没发现这个问题。吃个教训长记性吧。这题比赛的时候数据也出锅,结果重判后还是没通过orz
- 分块
#include "bits/stdc++.h" using namespace std; typedef long long LL; const LL MOD = 20180623; const LL MAXN = 10010; vector<LL> v[MAXN]; int main() { LL n, m, k; scanf("%lld%lld", &n, &m); for (LL i = 1; i <= n; i++) { scanf("%lld", &k); v[k].push_back(i); } for (LL i = 0; i <= 10000; i++) sort(v[i].begin(), v[i].end()); LL l1, r1, l2, r2, x; while (m--) { scanf("%lld%lld%lld%lld%lld", &l1, &r1, &l2, &r2, &x); if (x > 10000) { printf("0 0 0 "); continue; } if (l1 > r1) swap(l1, r1); if (l2 > r2) swap(l2, r2); LL a = upper_bound(v[x].begin(), v[x].end(), r1) - lower_bound(v[x].begin(), v[x].end(), l1); LL b = upper_bound(v[x].begin(), v[x].end(), r2) - lower_bound(v[x].begin(), v[x].end(), l2); printf("%lld %lld %lld ", a % MOD, b % MOD, a * b % MOD); } return 0; }
听说还可以用什么线段树主席树,树状数组套平衡树来做。但是我不会,哪天会了再来敲。
2019/6/21学完主席树来补题
sol:主席树,一开始用朴素的主席树做了一遍,但是感觉有效的只有叶子节点,大量的非叶子节点完全浪费了。然后就把树给稍作休整进行优化了。
- 朴素主席树
#include "cstdio" #include "algorithm" using namespace std; const int MAXN = 100010; const int MOD = 20180623; struct Node { int lson, rson; int cnt; } tree[MAXN * 20]; int tot, root[MAXN]; int build(int l, int r) { int i = ++tot; // tree[i].cnt = 0; if (l != r) { int mid = l + r >> 1; tree[i].lson = build(l, mid); tree[i].rson = build(mid + 1, r); } return i; } int add(int pre, int l, int r, int index) { int i = ++tot; tree[i] = tree[pre]; if (l == r) { tree[i].cnt++; } else { int mid = l + r >> 1; if (index <= mid) tree[i].lson = add(tree[pre].lson, l, mid, index); else tree[i].rson = add(tree[pre].rson, mid + 1, r, index); } return i; } int query(int i, int l, int r, int index) { if (l == r) return tree[i].cnt; int mid = l + r >> 1; if (index <= mid) return query(tree[i].lson, l, mid, index); else return query(tree[i].rson, mid + 1, r, index); } int main() { int n, m, k; scanf("%d%d", &n, &m); root[0] = build(1, 10000); for (int i = 1; i <= n; i++) { scanf("%d", &k); root[i] = add(root[i - 1], 1, 10000, k); } int l1, r1, l2, r2, x; while (m--) { scanf("%d%d%d%d%d", &l1, &r1, &l2, &r2, &x); if (x > 10000) { printf("0 0 0 "); continue; } if (l1 > r1) swap(l1, r1); if (l2 > r2) swap(l2, r2); int a = query(root[r1], 1, 10000, x) - query(root[l1 - 1], 1, 10000, x); int b = query(root[r2], 1, 10000, x) - query(root[l2 - 1], 1, 10000, x); int c = 1LL * a * b % MOD; printf("%d %d %d ", a, b, c); } return 0; }
- 优化版主席树
#include "bits/stdc++.h" using namespace std; const int MAXN = 100010; const int MOD = 20180623; struct Node { int lson, rson; int cnt; } tree[MAXN * 20]; int tot, root[MAXN]; // 利用二进制决定一个数字是父节点的左儿子还是右儿子 // 如 9 = 1001 从根节点出发就是右左左,从低位往高位扫,最高位的1忽略; void build(int i) { int now = 0; while (i != 1) { if (i & 1) { if (tree[now].rson == 0) tree[now].rson = ++tot; now = tree[now].rson; } else { if (tree[now].lson == 0) tree[now].lson = ++tot; now = tree[now].lson; } i >>= 1; } } int add(int pre, int index) { int i = ++tot; tree[i] = tree[pre]; if (index == 1) { tree[i].cnt++; } else { if (index & 1) tree[i].rson = add(tree[pre].rson, index >> 1); else tree[i].lson = add(tree[pre].lson, index >> 1); } return i; } int query(int i, int index) { if (index == 1) return tree[i].cnt; if (index & 1) return query(tree[i].rson, index >> 1); else return query(tree[i].lson, index >> 1); } int main() { int n, m, k; scanf("%d%d", &n, &m); // root[0] = 0; for (int i = 2; i <= 10000; i++) build(i); for (int i = 1; i <= n; i++) { scanf("%d", &k); root[i] = add(root[i - 1], k); } int l1, r1, l2, r2, x; while (m--) { scanf("%d%d%d%d%d", &l1, &r1, &l2, &r2, &x); if (x > 10000) { printf("0 0 0 "); continue; } if (l1 > r1) swap(l1, r1); if (l2 > r2) swap(l2, r2); int a = query(root[r1], x) - query(root[l1 - 1], x); int b = query(root[r2], x) - query(root[l2 - 1], x); int c = 1LL * a * b % MOD; printf("%d %d %d ", a, b, c); } return 0; }
优化后一棵树的大小为10000,每个节点都有效。但是由于路径问题,还是有一些重复的节点无法删除。
I .y大的字符串
sol:Trie+manacher,比赛的时候没开这题有点可惜,Trie是我最早学的数据结构,敲的很熟练但是没在比赛中碰到过。这次还是比赛结束之后看到题解补的。
ps:数据还是有问题,1M的字符串长度是1024 * 1024。但是字符串远不止这个长度。
- Trie + manacher
#include "cstdio" #include "cstdlib" #include "cstring" #include "algorithm" using namespace std; const int MAXN = 10250 * 1025; char s[15], t[MAXN * 2]; bool head[MAXN]; int p[MAXN * 2]; int _max; struct Trie { Trie* next[26]; bool tail; } *root; int max(int a, int b) {return a > b ? a : b;} int min(int a, int b) {return a < b ? a : b;} Trie* init() { Trie* p = (Trie*)malloc(sizeof(Trie)); memset(p->next, NULL, sizeof(p->next)); p->tail = false; return p; } void insert(char* s) { Trie* p = root; for (int i = 0; s[i]; i++) { int j = s[i] - 'a'; if (!p->next[j]) p->next[j] = init(); p = p->next[j]; } p->tail = true; } void search(char* s) { Trie* p = root; for (int i = 0; s[i]; i++) { int j = s[i] - 'a'; if (!p->next[j]) return; p = p->next[j]; if (p->tail) { head[s + i - t + 1] = true; _max = max(_max, s + i - t + 1); } } } int manacher(char* s, int* p) { int n = strlen(s); for (int i = n; i >= 0; i--) { s[i + 1 << 1] = s[i]; s[i << 1 | 1] = '#'; } s[0] = '$'; n = n + 1 << 1; int _max = 1, k = 0; for (int i = 2; s[i]; i++) { if (i >= k + p[k]) p[i] = 1; else p[i] = min(p[2 * k - i], k + p[k] - i); while (s[i + p[i]] == s[i - p[i]]) p[i]++; if (p[i] + i > p[k] + k) k = i; _max = max(_max, p[i]); } return _max - 1; } int main() { int n, m; scanf("%d%d", &n, &m); root = init(); while (n--) { scanf("%s", s); insert(s); } while (m--) { scanf("%s", t); _max = 0; memset(head, false, sizeof(head)); head[0] = true; for (int i = 0; t[i]; i++) if (head[i]) search(t + i); t[_max] = '