最近在复习数据结构,发现这套题不错,题目质量好,覆盖广,Data Structures部分包括Example,以及简单,中等,难三个部分,这几天把Example的做完了,
摘要如下:
通过这几题让我复习和练习了优先队列,并查集,并查集的路径压缩。
总结如下:
11995 - I Can Guess the Data Structure!
给出push,pop操作对应的序列,判定是stack, queue还是deque。
用上面三个数据结构对应模拟下push,pop的操作,将结果与输出进行比对,需要注意的是如果pop一个空的数据结构时,以上三者就都不是了。
#include <iostream> #include <queue> #include <stack> #include <stdio.h> using namespace std; int main() { // freopen("ismall.in", "r", stdin); // freopen("mysmall.ans", "w", stdout); int n; while (scanf("%d", &n) != EOF) { stack<int> s; queue<int> q; priority_queue<int> pq; bool is_stack = true; bool is_queue = true; bool is_priority_queue = true; for (int i = 0; i < n; i++) { int a, b; scanf("%d%d", &a, &b); if (a == 1) { s.push(b); q.push(b); pq.push(b); } else { if (s.empty()) { is_stack = is_queue = is_priority_queue = false; continue; } if (s.top() != b) { is_stack = false; } if (q.front() != b) { is_queue = false; } if (pq.top() != b) { is_priority_queue = false; } s.pop(); q.pop(); pq.pop(); } } if (!is_stack && !is_queue && !is_priority_queue) { printf("impossible\n"); } else if (is_stack && !is_queue && !is_priority_queue) { printf("stack\n"); } else if (!is_stack && is_queue && !is_priority_queue) { printf("queue\n"); } else if (!is_stack && !is_queue && is_priority_queue) { printf("priority queue\n"); } else { printf("not sure\n"); } } }
11991 - Easy Problem from Rujia Liu?
给定一个序列,有m次查询,每次查询给定一个数n,以及次数k,问说这个序列中n出现k次的索引。
每个数对应一个链表,从左往右,遇到每个数,对应将这个数的位置放进该数对应的链表中。
PS:把这题加强下,如果查询在一个区间内的某数n出现k次的位置的话,应该怎么做呢。
#include <iostream> #include <vector> #include <stdio.h> using namespace std; int main() { freopen("esmall.in", "r", stdin); freopen("mye", "w", stdout); int n, m; while (scanf("%d%d", &n, &m) != EOF) { vector<vector<int> > vv; vv.resize(1000001); for (int i = 0; i < n; i++) { int t; scanf("%d", &t); vv[t].push_back(i + 1); } while (m--) { int k, v; scanf("%d%d", &k, &v); if (k <= vv[v].size()) { printf("%d\n", vv[v][k - 1]); } else { printf("0\n"); } } }
有k个有序的序列(a, 2a, 3a, ..., ka), (b, 2b, 3b, ..., kb), ..., (x, 2x, 3x, ..., kx),这k^2个数中找出前k大的数。
将k个指针指向k个序列中尚未进入队列的最小的位置,每次从这些指针所指数中挑选出最小的数放进队列,然后将对应指针向后移动,当队列中达到k个时结束。从指针所指数中挑出最小数可以使用优先队列来操作,最后的复杂度是k*log(k)。
#include <iostream> #include <queue> #include <stdio.h> using namespace std; class Query { public: Query() {} Query(int q_num, int period) : seed_(1), q_num_(q_num), period_(period) {} int get_q_num() const { return q_num_; } int get_query_value() const { return seed_ * period_; } void increase() { seed_++; } bool operator < (const Query &q) const { if (this->get_query_value() != q.get_query_value()) { return this->get_query_value() > q.get_query_value(); } else { return this->get_q_num() > q.get_q_num(); } } private: int q_num_, period_, seed_; }; int main() { char ch[10]; priority_queue<Query> Q; while (scanf("%s", ch)) { if (ch[0] == '#') { break; } int q_num, period; scanf("%d%d", &q_num, &period); Q.push(Query(q_num, period)); } int K; scanf("%d", &K); while (K--) { Query top = Q.top(); Q.pop(); printf("%d\n", top.get_q_num()); top.increase(); Q.push(top); } }
有k个序列,从k个序列中各挑出一个数,求和得到sum,问说k^k种可能的sum中的前k小的sum是多少。
先将这k个序列排序,采用上题的算法思路,指针从位置i移到位置i+1,sum添加了num[i+1]-num[i],使用长度为k的pos{}数组记录k个序列的指针位置,将pos{}看做是图中一个节点的状态,从该状态最多可以扩展出k个状态,分别是k个位置的指针向后移动。扩展出的状态全部放入优先队列,最多扩展k次,每次最多扩展k个,因此最多有k^2个状态进队列,并做判重,最后挑出前k个值,有点类似Dijkstra算法。然而由于节点保存了pos{},因此节点对拷的时候,复杂度是O(k)的,所以复杂度是O(k^3*log(k)),还是TLE了。
#include <iostream> #include <algorithm> #include <vector> #include <queue> #include <set> #include <stdio.h> using namespace std; const int MAXK = 1000; int arr[MAXK][MAXK]; class Node { public: Node() {} Node(int k) : k_(k) { sum_ = 0; for (int i = 0; i < k_; i++) { idx_[i] = 0; sum_ += arr[i][0]; } } int get_idx(int i) const { return idx_[i]; } int get_sum() const { return sum_; } bool move(int i) { if (idx_[i] < k_) { idx_[i]++; sum_ += (arr[i][idx_[i]] - arr[i][idx_[i] - 1]); return true; } else { return false; } } bool operator < (const Node& node) const { if (sum_ != node.get_sum()) { return sum_ > node.get_sum(); } else { for (int i = 0; i < k_; i++) { if (idx_[i] != node.get_idx(i)) { return idx_[i] < node.get_idx(i); } } return false; } } private: int k_, sum_, idx_[MAXK]; }; int main() { //freopen("ksmall.in", "r", stdin); // freopen("k.in", "r", stdin); // freopen("my_k", "w", stdout); int k, c = 0; while (scanf("%d", &k) != EOF) { c++; priority_queue<Node> Q; set<Node> passed; for (int i = 0; i < k; i++) { for (int j = 0; j < k; j++) { scanf("%d", &arr[i][j]); } sort(arr[i], arr[i] + k); } Node init_node = Node(k); Q.push(init_node); passed.insert(init_node); for (int i = 0; i < k; i++) { Node top = Q.top(); Q.pop(); printf("%d ", top.get_sum()); for (int j = 0; j < k; j++) { Node next_node = top; if (next_node.move(j)) { if (passed.find(next_node) == passed.end()) { passed.insert(next_node); Q.push(next_node); } } } } printf("\n"); } }
能AC的一种做法是,将问题简化,首先看两个序列的情况a[0], a[1], ..., a[k], b[0], b[1], ..., b[k],两两求和的所有k^2的情况可以摆成k个长度为k的有序序列,a[i]+b[0], a[i]+b[1], ..., a[i]+b[k], (1 <= i <= k),然后该问题转换为上题的原型。到此,两条序列转变成了一条序列,该序列是这两条序列中和的前k小,用这条序列再和第三条序列做同样的操作,接下来同理,进行(k-1)次该操作后,就可以求出sum的前k小了,复杂度是O(k^2*log(k))。
#include <iostream> #include <algorithm> #include <queue> #include <stdio.h> using namespace std; const int MAXK = 1005; int arr[MAXK][MAXK]; struct Node { int idx, sum; bool operator < (const Node& node) const { return sum > node.sum; } }; int main() { //freopen("k.in", "r", stdin); //freopen("ksmall.in", "r", stdin); //freopen("k.out", "w", stdout); int k; int c = 0; while (scanf("%d", &k) != EOF) { c++; for (int i = 0; i < k; i++) { for (int j = 0; j < k; j++) { scanf("%d", &arr[i][j]); } sort(arr[i], arr[i] + k); } for (int i = 1; i < k; i++) { // arr[0][...], arr[i][...] priority_queue<Node> Q; for (int j = 0; j < k; j++) { Node node; node.idx = 0; node.sum = arr[0][j] + arr[i][0]; Q.push(node); } for (int j = 0; j < k; j++) { Node top = Q.top(); Q.pop(); arr[0][j] = top.sum; if (top.idx < k - 1) { top.sum = top.sum - arr[i][top.idx] + arr[i][top.idx + 1]; top.idx++; Q.push(top); } } } for (int i = 0; i < k; i++) { if (i > 0) { printf(" "); } printf("%d", arr[0][i]); } printf("\n"); } }
每次加一条边,当图中有环时不能加入。
其实就是Kruskal算法的并查集优化版,将边从小到大排序后,依次加入森林,当发现有环时跳过。其实并查集就是维护一个森林,每颗树标识着一个连通分量,每棵树有个固定的root节点,而路径压缩就是将该路径上的所有节点为根的小树都接到该连通分量的root下。
#include <iostream> #include <stdio.h> using namespace std; const int MAXN = 1e5 + 5; class UnionSet { public: UnionSet() {} UnionSet(int n) : n_(n) { father = new int[n_]; this->init(); } ~UnionSet() { if (NULL != father) { delete[] father; father = NULL; } } void init() { for (int i = 0; i < n_; i++) { father[i] = i; } } /*{{{ find_father*/ int find_father(int i) { if (father[i] == i) { return i; } else { return father[i] = find_father(father[i]); } } /*}}}*/ /*{{{ union_together */ bool union_together(int i, int j) { int father_i = this->find_father(i); int father_j = this->find_father(j); if (father_i != father_j) { father[father_j] = father_i; return true; } else { return false; } } /*}}}*/ private: int *father; int n_; }; int main() { int x, y, ans = 0; UnionSet union_set = UnionSet(MAXN); while (scanf("%d", &x) != EOF) { if (x == -1) { printf("%d\n", ans); ans = 0; union_set.init(); } else { scanf("%d", &y); if (union_set.union_together(x, y) == false) { ans++; } } } }
查询并查集中某节点到所在连通分量根的路径长度。
每个节点维护该节点到该节点父亲的路径长度,而要求该节点到root的长度,只需在该节点处,做一个路径压缩,该节点接在了root下,也就得到了该长度。
#include <iostream> #include <string.h> #include <stdio.h> #include <math.h> #include <cmath> using namespace std; const int MAXN = 20005; int father[MAXN]; // sum[i] record the sum from i to father[i] int sum[MAXN]; int find_father(int i) { if (i == father[i]) { sum[i] = 0; return i; } else { int f = find_father(father[i]); sum[i] += sum[father[i]]; return father[i] = f; } } void log(int n) { printf("sum; "); for (int i = 1; i <= n; i++) { find_father(i); printf("(%d)%d ", father[i], sum[i]); } printf("\n"); } int main() { int T; scanf("%d", &T); while (T--) { int n; scanf("%d", &n); for (int i = 1; i <= n; i++) { father[i] = i; sum[i] = 0; } char ch[2]; while (scanf("%s", ch)) { if (ch[0] == 'O') { break; } else if (ch[0] == 'I') { int a, b; scanf("%d%d", &a, &b); father[a] = b; sum[a] = (int)(abs(a - b)) % 1000; //log(n); } else if (ch[0] == 'E') { int c; scanf("%d", &c); find_father(c); printf("%d\n", sum[c]); } } } }