A – 小凯的疑惑
送命规律题。当然也可以考虑用 exgcd 的性质,以下是感性理解:
考虑设这个数为\(x \equiv ya {\pmod b}\),展开就是:
\[ x = ya + kb, k \in [1, b – 1] \]
发现\(k \geq 0\)时都满足,那么反过来取\(-1\)就可以又满足最大又满足不满足条件。
// P3951.cpp #include <iostream> #include <cstring> using namespace std; int main() { unsigned long long a, b, i; cin >> a >> b; cout << a * b - a - b; return 0; }
B – 时间复杂度
刚开始我觉得这道题目非常的可怕,最后我还是自己写了出来(因为题目给的格式还是很好的,没有想象中的困难)。用计数器记录次数、是否零次循环即可。
// P3952.cpp #include <bits/stdc++.h> using namespace std; char content[1010], opt[20]; int T, L, complexity, vis[300]; struct instruction { char typ, var_name[3]; bool tag, tag2; int x, y; instruction() { typ = 0, memset(var_name, 0, sizeof(var_name)); tag = tag2 = false; x = y = 0; } }; stack<instruction> st; int read() { int pos = 1, ret = 0; while (opt[pos] != 0) ret = ret * 10 + opt[pos] - '0', pos++; return ret; } int main() { scanf("%d", &T); while (T--) { memset(vis, 0, sizeof(vis)); // complexity reader; complexity = 0; memset(opt, 0, sizeof(opt)); scanf("%d%s", &L, opt + 1); if (strlen(opt + 1) == 4) complexity = (opt[3] == '1') ? 0 : 1; else for (int i = 5, siz = strlen(opt + 1); i <= siz - 1; i++) complexity = complexity * 10 + (opt[i] - '0'); // read the whole content; int mx_idx = 0; bool err_flag = false; while (!st.empty()) st.pop(); for (int i = 1, curt_complexity = 0, choke = 0; i <= L; i++) { instruction ist; ist.tag = false, ist.tag2 = false; scanf("%s", content + 1); ist.typ = content[1]; if (ist.typ == 'F') { scanf("%s", ist.var_name + 1); // repeated name; err_flag |= (vis[ist.var_name[1]] > 0); vis[ist.var_name[1]]++; // read the condition; scanf("%s", opt + 1); if (opt[1] == 'n') ist.x = -1; else ist.x = read(); scanf("%s", opt + 1); if (opt[1] == 'n') ist.y = -1; else ist.y = read(); if (ist.x != -1 && ist.y == -1) { // O(n); if (choke == 0) curt_complexity++, ist.tag2 = true; } else if (ist.x == -1 && ist.y != -1) choke++, ist.tag = true; else if (ist.x != -1 && ist.y != -1 && ist.x > ist.y) choke++, ist.tag = true; st.push(ist); } else if (ist.typ == 'E') { if (st.empty()) // nowhere to go; err_flag = true; else { instruction current_ist = st.top(); if (current_ist.tag) choke--; if (current_ist.tag2) curt_complexity--; vis[current_ist.var_name[1]]--; st.pop(); } } mx_idx = max(mx_idx, curt_complexity); } err_flag |= (!st.empty()); if (err_flag) { puts("ERR"); continue; } puts(mx_idx == complexity ? "Yes" : "No"); } return 0; }
C – 逛公园
策策主任逛机房。
这道题看上去超级难,但是最后用记忆化搜索的方式暴解起初让我意外,但是过了几秒之后真的觉得很妙。因为\(k < 50\),显然可以直接记忆化搜索状态进行转移(管他妈的转移顺序)。
设置状态\(dp[u][addition]\),其中\(addition\)代表的就是剩下的\(bonus\)成分,然后就可以开始暴力计数了。这个方法是真的好。
// P3953.cpp #include <bits/stdc++.h> #define ll long long #define pr pair<ll, int> using namespace std; const int MAX_N = 100100, MAX_E = 200010; int head[MAX_N], n, m, k, p, current, T; ll dist[MAX_N], dp[MAX_N][60]; bool vis[MAX_N], inst[MAX_N][60]; struct edge { int to, nxt; ll weight; bool tag; } edges[MAX_E << 1]; void addpath(int src, int dst, ll weight, bool toggle) { edges[current].to = dst, edges[current].nxt = head[src]; edges[current].tag = toggle, edges[current].weight = weight; head[src] = current++; } void shortest_path() { memset(dist, 0x3f, sizeof(dist)); memset(vis, false, sizeof(vis)); priority_queue<pr> pq; pq.push(make_pair(0, n)), dist[n] = 0; while (!pq.empty()) { int u = pq.top().second; pq.pop(); if (vis[u]) continue; vis[u] = true; for (int i = head[u]; i != -1; i = edges[i].nxt) if (edges[i].tag == true && dist[edges[i].to] > dist[u] + edges[i].weight) dist[edges[i].to] = dist[u] + edges[i].weight, pq.push(make_pair(-dist[edges[i].to], edges[i].to)); } } int dfs(int u, int addition) { if (inst[u][addition]) return -1; if (dp[u][addition]) return dp[u][addition]; inst[u][addition] = true; if (u == n) dp[u][addition] = 1; for (int i = head[u]; i != -1; i = edges[i].nxt) if (dist[edges[i].to] - dist[u] + edges[i].weight <= addition && edges[i].tag == true) { ll diff = dist[edges[i].to] - dist[u] + edges[i].weight; if (dfs(edges[i].to, addition - diff) == -1) return (dp[u][addition] = -1); dp[u][addition] = (1LL * dp[u][addition] + 1LL * dp[edges[i].to][addition - diff]) % p; } inst[u][addition] = false; return dp[u][addition]; } int main() { scanf("%d", &T); while (T--) { memset(head, -1, sizeof(head)), current = 0; memset(inst, 0, sizeof(inst)), memset(dp, 0, sizeof(dp)); scanf("%d%d%d%d", &n, &m, &k, &p); for (int i = 1, u, v, w; i <= m; i++) scanf("%d%d%d", &u, &v, &w), addpath(u, v, w, false), addpath(v, u, w, true); shortest_path(); for (int i = 0; i < current; i++) edges[i].tag ^= 1; printf("%d\n", dfs(1, k)); } return 0; }
D – 奶酪
sb 并查集搞定。
// P3958.cpp #include <iostream> #include <vector> #include <cmath> using namespace std; #define ll long long int n, h, r; struct Point { ll x, y, z; Point() {} Point(int x, int y, int z) { this->x = x; this->y = y; this->z = z; } }; struct Sphere { Point center; ll radius; bool upperColliding; bool lowerColliding; int index; Sphere() {} Sphere(Point c, int radius); }; double getDist(Point a, Point b); bool checkCollide(Sphere s1, Sphere s2); bool checkLowerCollide(Sphere s); bool checkUpperCollide(Sphere s); Sphere::Sphere(Point c, int radius) { center = c; this->radius = radius; } double getDist(Point a, Point b) { return sqrt(pow(a.x - b.x, 2) + pow(a.y - b.y, 2) + pow(a.z - b.z, 2)); } bool checkUpperCollide(Sphere s) { if (s.center.z + s.radius >= h) return true; return false; } bool checkLowerCollide(Sphere s) { if (s.center.z - s.radius <= 0) return true; return false; } bool checkCollide(Sphere s1, Sphere s2) { if (getDist(s1.center, s2.center) <= s1.radius + s2.radius) return true; return false; } struct Batch { vector<Sphere> spheres; vector<Sphere> upper; vector<Sphere> lower; int mem[100100]; void MakeHeap() { for (int i = 0; i < n + 2; i++) mem[i] = i; } void UnionElement(int p, int q) { int p_parent = FindElement(p); int q_element = FindElement(q); if (p_parent != q_element) mem[p_parent] = q_element; } int FindElement(int p) { if (mem[p] != p) mem[p] = FindElement(mem[p]); return mem[p]; } bool allFlag = true; bool goFlag = false; void initialize() { cin >> n >> h >> r; for (int i = 0; i < n; i++) { ll x, y, z; cin >> x >> y >> z; Sphere sss = Sphere(Point(x, y, z), r); sss.index = i; sss.upperColliding = checkUpperCollide(sss); sss.lowerColliding = checkLowerCollide(sss); if (sss.upperColliding && sss.lowerColliding) goFlag = true; if (sss.upperColliding) allFlag = false, upper.push_back(sss); if (sss.lowerColliding) lower.push_back(sss); spheres.push_back(sss); } return; } void preprocess() { MakeHeap(); for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) { if (i == j) continue; if (checkCollide(spheres[i], spheres[j])) UnionElement(i, j); } } void Solve() { if (goFlag) { cout << "Yes" << endl; return; } if (allFlag) { cout << "No" << endl; return; } preprocess(); for (int i = 0; i < upper.size(); i++) for (int j = 0; j < lower.size(); j++) { if (upper[i].index == lower[i].index) continue; int resi = FindElement(upper[i].index), resj = FindElement(lower[j].index); if (resi != resj) continue; cout << "Yes" << endl; return; } cout << "No" << endl; } }; int main() { int T; cin >> T; while (T--) { Batch b; b.initialize(); b.Solve(); } return 0; }
E – 宝藏
想到了状态之后就算是 sb 题了。设置状态\(dp[stat][i]\),stat 就是已有的点集,\(i\)是最大树高。知道这个之后枚举子集就可以搞定了(因为最大树高为最糟情况,且这个情况会由状态压缩自动处理掉,请自行思考)。
// P3959.cpp #include <bits/stdc++.h> using namespace std; const int MAX_N = 20, INF = 0x3f3f3f3f; int dist[MAX_N][MAX_N], n, m, dp[1 << MAX_N][MAX_N], dist_mask[1 << MAX_N]; int main() { memset(dist, 0x3f, sizeof(dist)); scanf("%d%d", &n, &m); for (int i = 1, u, v, w; i <= m; i++) scanf("%d%d%d", &u, &v, &w), u--, v--, dist[u][v] = dist[v][u] = min(dist[u][v], w); for (int i = 1; i < (1 << n); i++) for (int j = 0; j < n; j++) if (i & (1 << j)) { dist[j][j] = 0; for (int k = 0; k < n; k++) if (dist[j][k] != INF) dist_mask[i] |= (1 << k); } memset(dp, 0x3f, sizeof(dp)); for (int i = 0; i < n; i++) dp[1 << i][0] = 0; for (int stat = 2; stat < (1 << n); stat++) for (int subset = stat - 1; subset; subset = (subset - 1) & stat) if ((dist_mask[subset] | stat) == dist_mask[subset]) { int sum = 0, from = subset ^ stat; for (int k = 0; k < n; k++) if (from & (1 << k)) { int tmp = INF; for (int idx = 0; idx < n; idx++) if (subset & (1 << idx)) tmp = min(tmp, dist[idx][k]); sum += tmp; } for (int i = 1; i < n; i++) if (dp[subset][i - 1] != INF) dp[stat][i] = min(dp[stat][i], sum * i + dp[subset][i - 1]); } int ans = INF; for (int i = 0; i < n; i++) ans = min(ans, dp[(1 << n) - 1][i]); printf("%d", ans); return 0; }
F – 列队
50 分退役题。
首先有一个性质:每一次出队的时候,只会影响当前列和当前行和右下角的格子。所以,不难想到,维护一堆线段树来记录每一列某个位置前有多少个人,这样我们可以用权值线段树那一套,来二分出那个位置(每次多了一个点,加一之后就会自动偏移,非常巧妙)。超过范围的就用 vector 存就好。
当然,即使是这样,我还是不会。
// P3960.cpp #include <bits/stdc++.h> #define ll long long using namespace std; const int MAX_N = 3e5 + 200, SEG = 1e7 + 200; ll siz[SEG], n, m, q, ptot, upper; int lson[SEG], rson[SEG], roots[SEG]; vector<ll> vecs[MAX_N]; ll atRank(ll k, int l, int r, int p) { if (l == r) return l; ll mid = (l + r) >> 1, lsiz = mid - l + 1 - siz[lson[p]]; if (k <= lsiz) return atRank(k, l, mid, lson[p]); else return atRank(k - lsiz, mid + 1, r, rson[p]); } int update(int qx, int l, int r, int p) { if (p == 0) p = ++ptot; ++siz[p]; if (l == r) return p; int mid = (l + r) >> 1; if (qx <= mid) lson[p] = update(qx, l, mid, lson[p]); else rson[p] = update(qx, mid + 1, r, rson[p]); return p; } ll query_col(int x, int y) { ll tmp = atRank(y, 1, upper, roots[x]); roots[x] = update(tmp, 1, upper, roots[x]); return tmp < m ? 1LL * (x - 1) * m + tmp : vecs[x][tmp - m]; } ll query_row(int x) { ll tmp = atRank(x, 1, upper, roots[n + 1]); roots[n + 1] = update(tmp, 1, upper, roots[n + 1]); return tmp <= n ? 1LL * tmp * m : vecs[n + 1][tmp - n - 1]; } int main() { scanf("%lld%lld%lld", &n, &m, &q); upper = max(n, m) + q; while (q--) { int x, y; ll tmp; scanf("%d%d", &x, &y); if (y == m) tmp = query_row(x), vecs[n + 1].push_back(tmp), printf("%lld\n", tmp); else { tmp = query_col(x, y), vecs[n + 1].push_back(tmp); printf("%lld\n", tmp); tmp = query_row(x), vecs[x].push_back(tmp); } } return 0; }