15级算法第二次上机解题报告
来源:互联网 发布:人工智能 药物研发 编辑:程序博客网 时间:2024/06/16 00:02
A 数论の初见
解题思路:
首先使用Eratosthenes筛法求出内的素数,得到一个布尔数组vaild,vaild[i]表示数字i的素性,时间复杂度为O(MaxN*loglog(MaxN)),空间复杂度为O(MaxN)。
用数组sum存储所求答案,sum[i]表示小于等于i的素数的个数。由于sum[i]可以由sum[i-1]和vaild[i]得到,因此求出整个数组的时间复杂度为O(n),空间复杂度为O(n)。
对于每次询问n,只需要输出sum[n]即可,处理每次询问的复杂度都是O(1)。
综上,该解法的时间复杂度为O(MaxN*loglog(MaxN)+T),空间复杂度为O(MaxN),其中MaxN为N的最大值,T为询问组数。
Eratosthenes筛法:如果要判断在内的所有数的素性,在内从小到大枚举基数i,去掉范围内所有的i的倍数,枚举i直到超过为止,剩下的所有数都是素数。Eratosthenes筛法时间复杂度为O(n*loglogn),空间复杂度为O(n)。
附注:为什么在选定基数i后,不用考虑范围内的数?因为此范围内的数一定有比i小的因子,肯定在之前已经被删除出集合了。事实上,每个数在它的最小的素因子被选定为基数时,就一定会被删除出集合。
代码:
#include <iostream>using namespace std;const int MaxN = 1000000 + 7;bool valid[MaxN];int sum[MaxN];int num;//筛法求素数void getPrime(int n) { long long i, j; for (i = 0; i <= n; ++i) valid[i] = true; //筛选之前,都在集合里 for (i = 2; i <= n; ++i) if (valid[i]) { if (n / i < i) break; for (j = i * i; j < n; j += i) valid[j] = false; //去掉i的倍数 }}int main() { getPrime(MaxN - 1); sum[0] = sum[1] = 0; //边界情况,n=0或n=1 for (int i = 2; i < MaxN; i++) if (valid[i]) //如果i是素数 sum[i] = sum[i - 1] + 1; else //如果i不是素数 sum[i] = sum[i - 1]; while (cin >> num) cout << sum[num] << "\n"; //O(1)处理每个询问}
B 模式寻数
解题思路:
由于两个数组是相互独立的,因此题目要求即为分别求出两个数组中的最小值并相加。这里偷个懒,用std::nth_element()求出最小值,然后相加,程序相当简洁。
时间复杂度:O(n),空间复杂度:O(n)
代码:
#include <cstdio>#include <algorithm>const int MaxN = 4000 + 7;int n;int arr1[MaxN], arr2[MaxN];int main() { while (scanf("%d", &n) != EOF) { for (int i = 0; i < n; i++) scanf("%d", &arr1[i]); for (int i = 0; i < n; i++) scanf("%d", &arr2[i]); std::nth_element(arr1, arr1, arr1 + n); //求出第一个数组中最小的数 std::nth_element(arr2, arr2, arr2 + n); //求出第二个数组中最小的数 printf("%d\n", arr1[0] + arr2[0]); }}
C jhljx水水的补习班
解题思路:
由题意,每两个学生之间都有相对的优先级关系。题目需要实现在线插入元素、查询最大元素、删除最大元素,可以使用优先队列实现。
通过建类重载小于号的做法,使用STL优先队列实现。
时间复杂度:O(mlogm+m),空间复杂度:O(n)
代码:
#include <cstdio>#include <queue>using namespace std;const int MaxN = 100000 + 7;int n, m;int s, g, sc, arg1;char arg[15];//学生类,用于判定ljx更喜欢哪一个class myStudent {public: myStudent() {} myStudent(int _s, int _g, int _sc) : s(_s), g(_g), sc(_sc) {} int s, g, sc; //重载小于号,以便使用优先队列 bool operator<(const myStudent &s1) const { if (sc != s1.sc) return sc < s1.sc; if (g != s1.g) return g > s1.g; return s > s1.s; }} student[MaxN];priority_queue<myStudent, vector<myStudent> > pq;int main() { while (scanf("%d%d", &n, &m) != EOF) { //先清空,以防意外 while (!pq.empty()) pq.pop(); //读入所有学生的信息 for (int i = 1; i <= n; i++) { scanf("%d%d%d", &s, &g, &sc); student[i] = myStudent(s, g, sc); } //读入操作 for (int i = 1; i <= m; i++) { scanf("%s", arg); if (arg[0] == 'A') { //Add scanf("%d", &arg1); pq.push(student[arg1]); } else if (arg[0] == 'D') { //Delete if (!pq.empty()) pq.pop(); } else if (arg[0] == 'Q') { //Query printf("%d %d %d\n", pq.top().s, pq.top().g, pq.top().sc); } } }}
D jhljx水水的最短路径
解题思路:
单源最短路问题是一个经典的问题,常见的算法有Dijkstra算法和Bellman-Ford算法。对于点数为V,边数为E的图,使用斐波那契堆(如SGI-STL优先队列)优化的Dijkstra算法的时间复杂度为O(E+VlogV);而使用普通队列优化的Bellman-Ford算法(又称SPFA算法)的时间复杂度为平均O(kE),k由图的稠密情况决定,最坏情况为O(VE)。使用优先队列优化的SPFA有时会更快一些,但有时也会比普通队列的版本慢。
SPFA算法的做法为:
设立一个队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点插入队列。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。
时间复杂度:O(kE),空间复杂度:O(V+E)
代码:
#include <iostream>#include <cstdio>#include <vector>#include <queue>#include <cstring>using namespace std;const int MaxN = 100000 + 7;const int INF = 0x3f3f3f3f;int n, m, x, y, k;//边结构体struct Edge { int v; int cost; Edge(int _v = 0, int _cost = 0) : v(_v), cost(_cost) {}};//边数组vector<Edge> E[MaxN];//加边函数void addedge(int u, int v, int w) { E[u].push_back(Edge(v, w)); }bool vis[MaxN];//记录是否在优先队列中int cnt[MaxN];int dist[MaxN];//最终距离bool SPFA(int start, int n) { memset(vis, false, sizeof(vis)); for (int i = 1; i <= n; i++)dist[i] = INF; vis[start] = true; dist[start] = 0; queue<int> que; while (!que.empty())que.pop(); //插入起点 que.push(start); memset(cnt, 0, sizeof(cnt)); cnt[start] = 1; //每次取出队首 while (!que.empty()) { int u = que.front(); que.pop(); vis[u] = false; for (int i = 0; i < E[u].size(); i++) { int v = E[u][i].v; if (dist[v] > dist[u] + E[u][i].cost) { dist[v] = dist[u] + E[u][i].cost; if (!vis[v]) { vis[v] = true; que.push(v); if (++cnt[v] > n)return false; } } } } return true;}int main() { while (scanf("%d%d", &n, &m) != EOF) { //清空防止意外 for (int i = 0; i < MaxN; i++) E[i].clear(); //读边加边 for (int i = 0; i < m; i++) { scanf("%d%d%d", &x, &y, &k); addedge(x, y, k); addedge(y, x, k);//切记,无向图需要加入反向边 } //执行SPFA SPFA(1, n); //将无法到达的点的距离设为-1 for (int i = 2; i <= n; i++) if (dist[i] == INF) dist[i] = -1; //输出 for (int i = 2; i <= n; i++) printf("%d ", dist[i]); printf("\n"); }}
E 我有特殊的快排技巧
解题思路:
求中位数,可以转化为一个更强的问题:求第K小数。
做法如下:从序列中取一个数mid,然后把序列分成小于mid和大于mid的两部分。由两个部分的元素个数和k的大小关系可以确定这个数在哪个部分,对部分序列的探查可以递归处理。
序列中每个元素最多被访问到一次,因此该算法的时间复杂度是O(n)的。
时间复杂度:O(n),空间复杂度:O(n)
代码(超级霸气STL版):
#include <iostream>#include <algorithm>using namespace std;const int MaxN = 100000 + 7;int n,pos;int num[MaxN];int main() { while (cin >> n) { for (int i = 0; i < n; i++) cin >> num[i]; //读入 pos = (n + 1) / 2; //中位数的位置 nth_element(num, num + pos - 1, num + n);//STL大法 cout << num[pos - 1] << "\n"; }}
代码(细节无损手工版):
#include <iostream>#include <algorithm>using namespace std;const int MaxN = 100000 + 7;int n, pos;int num[MaxN];/* * 划分函数的加强版 * 在a[l],a[r]中,选取最中间的元素作为mid * 根据此元素进行左右划分 * 使其左边的数都小于等于它,右边的数都大于等于它 * rank表示需要找到第rank小的数 */void quickSelect(int a[], int l, int r, int rank) { int i = l, j = r, mid = a[(l + r) / 2]; //缩小区间,找到第一组不符合左小右大的数 while (i <= j) { while (a[i] < mid) ++i; while (a[j] > mid) --j; if (i <= j) { swap(a[i], a[j]); ++i; --j; } } //对于rank所在的区域进行递归查找 if (l <= j && rank<=j - l + 1) quickSelect(a, l, j, rank); if (i <= r && rank>=i - l + 1) quickSelect(a, i, r, rank-(i-l));}/* 为了使递归调用的函数更加优雅 * 也为了能直接通过返回值得到想要的数 * 对上面的函数进行再次封装 * 返回值即为第K小数 */int quick_select(int a[], int n, int k) { quickSelect(a, 1, n, k); return a[k];}int main() { while (cin >> n) { for (int i = 1; i <= n; i++) cin >> num[i]; pos = (n + 1) / 2; //中位数的位置 cout << quick_select(num, n, pos) << "\n"; }}
F 四合归零
解题思路:
要从4个长度为n的数组中各选一个数进行求和,如果直接暴力枚举,复杂度为O(),显然超时。
可以采用以下做法:
- 将数组进行两两合并,形成一个大小为的数组。
- 再将这两个数组里每个元素的值分别映射到两个数组上,例如a1[i]=k转化为sum1[k]=1,用于统计这两个数组里特定的值各有多少个(以刚刚的转化为例,就统计了值为k的数有一个),并做到O(1)时间内进行查询。
- 将映射后的两个数组的每一个值进行查询,对于每一个值,在另一个数组里寻找它的相反数,二者个数相乘就是符合这一规则的数的个数。
时间复杂度:O(),空间复杂度:O(+MaxNum), 其中MaxNum表示元素的最大值与最小值之差。
代码:
#include <cstdio>#include <cstring>const int inf = 2 * 500000 + 7;const int MaxN = 40000 + 7;int n;int arr1[MaxN], arr2[MaxN], arr3[MaxN], arr4[MaxN];long long ans;/* 接下来两个数组就是映射后的数组 * 由于值可能为负,因此无论存取,都偏移inf个地址 * 例如,sum1[2+inf]存储的是值为2的元素的个数 * inf为MaxNum值的两倍 */int sum1[inf * 2], sum2[inf * 2];int main() { while (scanf("%d", &n) != EOF) { //国际惯例,用前清空,跟饭前洗手一个道理 memset(sum1, 0, sizeof(sum1)); memset(sum2, 0, sizeof(sum2)); //读入四个原始数组,写的比较丑 for (int i = 0; i < n; i++) scanf("%d", &arr1[i]); for (int i = 0; i < n; i++) scanf("%d", &arr2[i]); for (int i = 0; i < n; i++) scanf("%d", &arr3[i]); for (int i = 0; i < n; i++) scanf("%d", &arr4[i]); //这里将思路中的12两步合并 for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) sum1[arr1[i] + arr2[j] + inf]++; for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) sum2[arr3[i] + arr4[j] + inf]++; //对于每个值,找到它的相反数 ans = 0; for (int i = (inf * -1) + 1; i < inf; i++) ans = ans + sum1[i + inf] * sum2[-i + inf]; printf("%lld\n", ans); }}
- 15级算法第二次上机解题报告
- 16级C++第二次上机解题报告
- 15级算法第一次上机解题报告
- 15级算法第三次上机解题报告
- 【作业解答】第二次上机作业解题报告
- 北京航空航天大学2014第二次上机解题报告
- 2016级计算机C++助教工作(12) 第二次上机解题报告
- 第二次上机赛解题报告及标程
- 第二次上机指导报告
- 第二次C上机报告
- 第二次程序设计上机报告
- 第二次上机报告
- 第二次上机报告
- 第二次上机报告
- 第二次上机实验报告
- 第二次上机实验报告
- 第二次上机实验报告
- 第二次上机实验报告
- PrimeSense资料杂烩
- Linux命令之"ss"
- linux中使得g++支持C++11的方法
- 许愿2016
- 一步一步开始FPGA逻辑设计-学习资料篇
- 15级算法第二次上机解题报告
- 批处理的简单网络爬虫
- Bootstrap实现导航栏的两种方式
- 最常用和最难用的控件——ListView
- 使用VS2008建立C工程和编译C程序
- 剑指Offer面试题61:按之子型打印二叉树 Java实现
- 二叉树的前序, 中序, 后序非递归算法
- 【19.05%】【codeforces 731F】 Video Cards
- 3-2编程求解质数