C题(GCD Table) 题目链接: http://codeforces.com/contest/583/problem/C
题目大意: 定义 gcd 矩阵 b 为数组 a 中的元素两两求 GCD 得到, 即 b[i][j] = GCD(a[i], a[j]), 现给出数组 b 中所有数的乱序,求数组 a 中的元素,存在多种解时,给出任意一个解的任意一种排列。
解题思路: 观察矩阵 b 的结构可知,首先矩阵 b 是一个对称矩阵,我们所求的其实就是矩阵 b 的主对角元素,其次矩阵 b 的对角元素其实是所在行和列的最大的元素。
由此我们可以这么求解这道题(以 N <= 2 为例), 首先从所给的 N * N 个数的集合中选出最大的两个值, 这两个值一定是答案中的值,把它们放入答案中并从集合中删掉,然后求出这两个值的 GCD, 把这个 GCD 删掉,再找出剩下的所有数中的最大值放入答案中并从原来的集合中删掉, 以此类推, 最终可以求出需要的 N 个数。
贴出代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #include <map> #include <cmath> #include <queue> #include <stack> #include <vector> #include <cstdio> #include <string> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define lson l, mid, ls #define rson mid, r, rs #define ls (rt << 1) + 1 #define rs (rt << 1) + 2 using namespace std ;int res[550 ];map <int , int > ma;map <int , int >::reverse_iterator riter;int GCD (int a, int b) { if (a < b) return GCD(b, a); else if (a == 0 ) return b; return GCD(b % a, a); } int main () { int n, v; while (~scanf ("%d" , &n)){ for (int i = 0 ; i < n * n; i++){ scanf ("%d" , &v); ma[v]++; } int cnt = 0 ; while (cnt < n){ riter = ma.rbegin(); int tmp = riter -> first; ma[tmp]--; if (ma[tmp] == 0 ) ma.erase(tmp); for (int i = 0 ; i < cnt; i++){ int gcd = GCD(res[i], tmp); ma[gcd] -= 2 ; if (ma[gcd] == 0 ) ma.erase(gcd); } res[cnt++] = tmp; } for (int i = 0 ; i < cnt; i++) printf ("%d%c" , res[i], i == cnt - 1 ? '\n' : ' ' ); } return 0 ; }
D题(Once Again) 题目链接:http://codeforces.com/contest/583/problem/D
题目大意: 有一个元素个数为 n * t 的数组,数组中的元素满足 $a[i] = a[i + n]$, 求解最长不降子序列的长度。
数据范围:$1 <= n <= 100, 1 <= t <= 10^7, 1 <= a[i] <= 300$;
解题思路:
当 t < n 时, 直接 n(log(n))求解最长不减子序列。
当 t <= n 时, 通过观察可以知道,最多取 n 次就可以将前n个元素完全取到(比如序列 5, 4, 3, 2,1 我们第一次取 1, 第二次取 2, …… 第五次取 5, 这样取五次就可以取完,然而对于 1, 2, 3, 4, 5 这个序列只需要一次就可以取完所有的数), 所以对于 t <= n 的情况我们可以直接求解 n * n 这么个长度, 然后再加上我们抛弃的那一段某些数字的重复( 比如样例 n = 4, t = 3, a[i] = 3, 1, 4, 2 , 我们最终选择的序列为 1, 2, 3, 4, 4,在这里也就是最后选择了 4 不断重复)。
需要注意的是 1, 2, 3, 3, 4, 这种情况,在这种情况下,我们最终取到的最长不降子序列应该是 1, 2, 3, 3, 3, 3, … 3, 3, 4. 也就是说我们选择的那个不断重复的数字应该是在 a[1….n] 中重复出现最多的那个数字。
贴出代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 #include <map> #include <cmath> #include <queue> #include <stack> #include <vector> #include <cstdio> #include <string> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define lson l, mid, ls #define rson mid, r, rs #define ls (rt << 1) + 1 #define rs (rt << 1) + 2 using namespace std ;const int MAXN = 11000 ; int a[MAXN];int b[MAXN];int cnt[MAXN];int Find (int i, int R) { int l = 0 , r = R, mid; while (r - l < 1 ){ mid = (l + r) /2 ; if (b[mid] <= a[i]) l = mid; else r = mid; } return r; } int main () { int n, t; while (scanf ("%d%d" , &n, &t) != EOF){ memset (cnt, 0 , sizeof (cnt)); for (int i = 1 ; i <= n; i++){ scanf ("%d" , &a[i]); cnt[a[i]]++; } int num = 0 ; for (int i = 0 ; i < 330 ; i++) if (cnt[i]) num = max(num, cnt[i]); int end = min(t, n), dx = n + 1 ; for (int j = 1 ; j < end; j++) for (int i = 1 ; i <= n; i++) a[dx++] = a[i]; int ans = 1 ; b[1 ] = a[1 ]; for (int i = 2 ; i < dx; i++){ if (a[i] <= b[ans]) b[++ans] = a[i]; else b[Find(i, ans)] = a[i]; } ans += num * max(t - n, 0 ); printf ("%d\n" , ans); } return 0 ; }