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(),显然超时。

可以采用以下做法:

  1. 将数组进行两两合并,形成一个大小为的数组。
  2. 再将这两个数组里每个元素的值分别映射到两个数组上,例如a1[i]=k转化为sum1[k]=1,用于统计这两个数组里特定的值各有多少个(以刚刚的转化为例,就统计了值为k的数有一个),并做到O(1)时间内进行查询。
  3. 将映射后的两个数组的每一个值进行查询,对于每一个值,在另一个数组里寻找它的相反数,二者个数相乘就是符合这一规则的数的个数。

 

时间复杂度: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);    }} 


0 0