# aass3 **Repository Path**: tianq02/aass3 ## Basic Information - **Project Name**: aass3 - **Description**: CHD 24 fall Advanced Algorithm Design and Analysis, Assignment 3 - **Primary Language**: C++ - **License**: AGPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-12-04 - **Last Updated**: 2024-12-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Assignment 3 > CHD 24 fall Advanced Algorithm Design and Analysis ## [3-1](https://gitee.com/tianq02/aass3/blob/master/p1.cpp) ### Description: > 一辆虚拟汽车在加满油之后可以行驶 $n$ km。旅途中有 $k$ 个加油站。设计一个有效算法,使得沿途加油的次数最少。 > 终端输入正整数 $n$ 和 $k$,表示汽车加满油后可行驶 $n$ km,且旅途中有 $k$ 个加油站。 > 接下来输入 $k+1$ 个整数,表示第 $k$ 个加油站与第 $k-1$ 个加油站之间的距离。 > 第 $0$ 个加油站表示出发地,汽车已加满油。第 $k+1$ 个加油站表示目的地。 > After filling up the gas tank, a virtual car can travel a distance of $n$ km. There are $k$ gas stations along the > journey. Design an efficient algorithm to minimize the number of refueling stops. The input includes positive > integers $n$ and $k$, representing the car's range after a full tank ($n$ km), and the number of gas stations ($k$). > Following that, input $k+1$ integers representing the distances between consecutive gas stations. The $0$th gas > station > represents the starting point with a full tank, and the $k+1$th gas station represents the destination. ### Solution: 贪心算法,设置状态集合map,每个状态<剩余燃油,加油次数>,对于每个输入的加油站间距,迭代状态集合 1. 初始化,插入状态 <油箱容积,0> 2. 创建新的状态集合newMap,存储迭代结果 3. 对于 剩余燃油<间距 的状态,开不到下一个加油站,移除 4. 创建新状态 <剩余燃油-间距,加油次数>, 表示在下一个加油站不加油 5. 创建新状态 <油箱容积,加油次数+1>, 表示在下一个加油站加油 6. 处理完所有状态,map=newMap 7. 到达终点,输出map中最小的加油次数 由于题干有歧义,此处作两种版本 1. 求最小加油次数
```c++ #include #include #include #include using namespace std; int main() { int n, k; // n: fuel tank size, k: gas stations count vector distances; cin >> n >> k; for (int i = 0; i < k; i++) { int tmp = 0; cin >> tmp; distances.emplace_back(tmp); } int minRefill = 0; // 0: in case there is on stations between unordered_map map; // map.emplace(n, 0); for (const int &ahead: distances) { unordered_map newMap; // minRefill = numeric_limits::max(); for (const auto &[remain, refill]: map) { if (remain < ahead) continue; // we can't reach next station if (refill < minRefill) minRefill = refill; if (int curRemain = remain - ahead; !newMap.contains(curRemain) || refill < newMap[curRemain]) { newMap[curRemain] = refill; } if (!newMap.contains(n) || refill + 1 < newMap[n]) newMap[n] = refill + 1; } map = newMap; } cout << minRefill << endl; return 0; } ``` 2. 求加油次数最小时的加油站选择 思想:将前面版本的加油次数换成一串加油站序号 此处只给出一种可行的策略,如果需要给出所有策略可以将更新处改成拉链 ```c++ #include #include #include #include using namespace std; int main() { int n, k; vector distances; // distance[0] means road from station 0 (start) to station 1 cin >> n >> k; for (int i = 0; i < k; i++){ int tmp = 0; cin >> tmp; distances.emplace_back(tmp); } // plus version, basically changing from a single counting number to a vector of station ids int minRefill = numeric_limits::max(); // count min Refill rather than updating whole vector, this should save some time unordered_map> map; // > map.emplace(n, 0); // {GasRemain: n, RefillList: {}} note that 0 here means 0 for initial capacity for (int i=0; i::max(); unordered_map> newMap; for (const auto &[remain, refills]: map){ if (remain < ahead) continue; if (refills.size() < minRefill) minRefill = refills.size(); int newRemain = remain-ahead; if (!newMap.contains(newRemain) || refills.size() < newMap[newRemain].size()) { newMap[newRemain] = refills; } if (!newMap.contains(n) || refills.size()+1 < newMap[n].size()) { newMap[n] = refills; // since we are using const reference, we can't append Station Id here newMap[n].emplace_back(i+1); // skipping 0, since we are leaving 0 for 1, the refill happens at 1 } } map.swap(newMap); } for (const auto &[remain, refills]: map){ if (refills.size() > minRefill) continue; for (const int &id : refills) cout << id << " "; cout << endl; break; } return 0; } ``` ## [3-2](https://gitee.com/tianq02/aass3/blob/master/p2.cpp) ### Description: > 设 $x_1, x_2, \dots , x_n$ 是实直线上的 $n$ 个点。用固定长度的闭区间覆盖这 $n$ 个点,至少需要多少个 > 这样的固定长度闭区间?对于给定的实直线上的 $n$ 个点和闭区间的长度 $k$,设计解此问题的有效算法,计算覆盖点集的最少区间数。 > Let $x_1, x_2, \dots , x_n$ be $n$ points on the real line. To cover these $n$ points with fixed-length > closed intervals, what is the minimum number of such fixed-length closed intervals needed? > For a given set of $n$ points on the real line and a length $k$ of the closed intervals, > design an efficient algorithm to solve this problem > and calculate the minimum number of intervals required to cover the set of points. ### Solution 这道题和前面的很像,每个点相当于加油站,油箱容积相当于闭区间的长度,不同的是此处不再要求区间不中断:区间不必重叠 想象一支笔画线,从左到右依次经过各点$x_1, x_2, \dots , x_n$,在$x_i$处一定有墨迹。 在每个点处,都可以选择画线/不画线,该点处的状态表示为{剩余距离: 画线次数} 加入下一个点时,更新每一个状态: 1. 若剩余距离大于等于间距,可以不添加新的线段,加入新状态{剩余距离-间距: 画线次数} 2. ~~若剩余距离小于间距,当前线段无法到达下一点,有必要增加线段~~ 3. 无论剩余距离是否足够,加入新状态{线段长度: 画线次数+1},新增线段 这是由贪心算法的思想决定的,要查找所有可能的动作,将该状态更新为画线次数最少的一个 由此,求解该问题只需将[问题一:求最小加油次数](#solution-1)中的更新逻辑稍微修改,将加油的逻辑移到判定之前 实现中需要先将数组排序,因而此处的时间复杂度无法低于$O(n \log n)$ 为了避免浮点数预处理的精度损失,剩余距离不妨换成终点位置 1. 终点位置在点之后,保留该状态{终点位置:画线次数} 2. 新增状态:{该点位置+线段长度:画线次数+1} ```c++ #include #include #include #include #include #include using namespace std; int main() { int n; // point count double k; // (closed) interval length vector points; // coordinates of each point cin >> n >> k; for (int i = 0; i < n; i++) { double tmp; cin >> tmp; points.emplace_back(tmp); } ranges::sort(points); unordered_map map; map.emplace(points[0] + k, 1); // repeating the first point on init, this should have no effect for (const auto &pos: points) { unordered_map newMap; for (const auto &[endPos, count]: map) { // start a new interval here, update interval count if (!newMap.contains(pos+k) || count + 1 < newMap[pos+k]) newMap.emplace(pos+k, count + 1); // current interval can reach this point, keep this state unchanged if (endPos < pos) continue; newMap.emplace(endPos, count); } map.swap(newMap); } int minCount = numeric_limits::max(); for (const int &count: map | std::views::values) { minCount = min(minCount, count); } cout << minCount << endl; return 0; } ``` ## [3-3](https://gitee.com/tianq02/aass3/blob/master/p3.cpp) ### Description: [AcWing_6059](https://www.acwing.com/problem/content/description/6062/) > 【数列极差】问题描述:在黑板上写了 $N$ 个正整数组成的一个数列,进行如下操作:每一次擦去其中的两个数 $a$ 和 $b$, > 然后在数列中加入一个数 $a*b+1$,如此下去直至黑板上剩下一个数,在所有按这种操作方式得到的所有数中,最大的 $\max$, > 最小的为 $\min$,则该数列的极差定义为 $M = \max - \min$。试设计一个算法,计算 $M$。 > 【 Sequence Extreme Difference 】 Problem Description: A sequence composed of $N$ positive > integers is written on the blackboard. The following operation is performed: erase two numbers $a$ > and $b$, then add a number $a*b+1$ to the sequence. Repeat this process until only one number remains > on the blackboard. Among all the numbers obtained by this operation, let the maximum be $\max$ and > the minimum be $\min$. The extreme difference $M$ of the sequence is defined as $M = \max - \min$. > Design an algorithm to calculate $M$. ### Solution 分析问题: 1. 输入:有限个数的数列 2. 输出:可能算出来的最大和最小数值之间的差 3. 目标:确定两种选取方案,分别求得最大者和最小值 解法1:暴力搜索:[p3brute](https://gitee.com/tianq02/aass3/blob/master/p3brute.cpp) - 时间复杂度 $O(n!^2)$ - 空间复杂度 $O(n!)$ 很显然,这道题不能暴力求解,我们需要一点**注意到** - 每次选取两个较小的数合并,最终结果最大 - 每次选取两个较大的数合并,最终结果最小 ```c++ #include #include #include using namespace std; int main() { int n = 0; vector numbers; cin >> n; for (int i = 0; i < n; i++) { int tmp; cin >> tmp; numbers.emplace_back(tmp); } // ideology // by merging smallest 2 each time, we get the biggest one // by merging greatest 2 each time, we get the smallest one // don't just take my words, try it on paper vector maxNums = numbers, minNums = numbers; ranges::sort(maxNums); while (maxNums.size() > 1) { int newNum = maxNums[0] * maxNums[1] + 1; maxNums.erase(maxNums.begin(), maxNums.begin() + 2); // since our vector is already ordered, we don't need to sort again auto it = ranges::lower_bound(maxNums, newNum); maxNums.emplace(it, newNum); } ranges::sort(minNums, greater()); while (minNums.size() > 1) { int newNum = minNums[0] * minNums[1] + 1; minNums.erase(minNums.begin(), minNums.begin()+2); // the product of two biggest numbers should still be biggest minNums.emplace(minNums.begin(), newNum); } cout << maxNums[0] - minNums[0] << endl; } ``` ## [3-4](https://gitee.com/tianq02/aass3/blob/master/p4.cpp) ### Description: > 在一个圆形操场的四周摆放着 n 堆石子。现要将石子有次序地合并成一堆。规定每次只能选 > 相邻的 2 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。试设计一个算法, > 计算出将 n 堆石子合并成一堆的最小得分和最大得分。 > Around a circular playground, there are n piles of stones arranged. The task is to merge the stones > into a single pile in a certain order. It is stipulated that only adjacent piles of stones can be selected > at a time to merge into a new pile, and the number of stones in the new pile is recorded as the score > for that merging operation. Please design an algorithm to calculate the minimum and maximum > scores to merge n piles of stones into a single pile. ### Solution: 和p3很像,略微修改[p3brute](https://gitee.com/tianq02/aass3/blob/master/p3brute.cpp)即可得到本题的暴力解法[p4brute](https://gitee.com/tianq02/aass3/blob/master/p4brute.cpp) 它的时空复杂度非常糟糕,但可以给我们一些ground truth 我们**注意到**: - 每次合并两个**最少**的堆,所得分数**最少**, - 每次合并两个**最多**的堆,所得分数**最高** 此处的实现基于[solution3](https://gitee.com/tianq02/aass3/blob/master/submit/solution3.cpp),加入了一点点修改 ```c++ #include #include #include using namespace std; int main() { int n = 0; vector numbers; cin >> n; for (int i = 0; i < n; i++) { int tmp; cin >> tmp; numbers.push_back(tmp); } // ideology // by merging smallest 2 each time, we get the smallest score // by merging greatest 2 each time, we get the greatest score // don't just take my words, try it on paper vector minNums = numbers, maxNums = numbers; int minScore=0, maxScore = 0; ranges::sort(minNums); while (minNums.size() > 1) { int newNum = minNums[0] + minNums[1]; minScore += newNum; minNums.erase(minNums.begin(), minNums.begin() + 2); auto it = ranges::lower_bound(minNums, newNum); minNums.emplace(it, newNum); } ranges::sort(maxNums, greater()); while (maxNums.size() > 1) { int newNum = maxNums[0] + maxNums[1]; maxScore += newNum; maxNums.erase(maxNums.begin(), maxNums.begin()+2); // the product of two biggest numbers should still be biggest maxNums.emplace(maxNums.begin(), newNum); } cout << maxScore - minScore << endl; } ``` ## [3-5](https://gitee.com/tianq02/aass3/blob/master/p5.cpp) > 我无法独立完成此题目,此处参考网上的题解 [turing.1357](https://acm.webturing.com/problem.php?id=1357) ### Description: > 一个长、宽、高分别为 m, n, p 的长方体被分割成 $m*n*p$ 个小立方体。每个小立方体内有 > 一个整数,设计一个算法,计算出所给长方体的最大子长方体。子长方体的大小由它所含的 > 所有整数之和确定。 > A cuboid with dimensions length, width, and height of m, n, and p respectively, is divided into > $m*n*p$ small cubes. Each small cube contains an integer. Design an algorithm to calculate the > maximum sub-cuboid within the given cuboid. The size of the sub-cuboid is determined by the sum > of all the integers it contains. > 特别说明:整数**有正有负**,不是单纯求和 **输入样例** ```plaintext 3 3 3 0 -1 2 1 2 2 1 1 -2 -2 -1 -1 -3 3 -2 -2 -3 1 -2 3 3 0 1 3 2 1 -3 ``` **输出样例** ```plaintext 14 ``` ### Solution: 这道题是典型的动态规划,要应用化归的思想 ```c++ // 8601 最大长方体问题.cpp // #include using namespace std; //长方体:m行,n列,p层 //[m+1,n+1,p+1] int MaxSum(int n,int *a){ int sum=0; int b=0; for(int i=1;i<=n;i++){ if(b>0)b+=a[i]; else b=a[i]; if(sumsum)sum=max; } } free(b); return sum; } int MaxSum3(int m, int n, int p, int ***a){ int sum=0; int **b = new int*[n+1]; //动态分配数组[m+1,n+1] for(int i=1;i<=n;i++){ b[i]=new int[p+1]; } for(int i=1;i<=m;i++){//从第1层开始的所有平面 第2层、第3层... for(int j=1;j<=n;j++){ for(int k=1;k<=p;k++){ b[j][k] =0; } } for(int t=i;t<=m;t++){//第1层,第1层到第2层,第1层到第3层... 即第i层开始到第t层 for(int j=1;j<=n;j++){ for(int k=1;k<=p;k++){ b[j][k] += a[t][j][k]; //3维压缩为2维,即相加 } } int max = MaxSum2(n,p,b); //粗心 把这2行代码放到for循环外面了 if(max>sum)sum=max; //粗心 把这2行代码放到for循环外面了 //放里面才是正确的 } } free(b); return sum; } int main() { int m,n,p; cin>>m>>n>>p; int ***a = new int **[m+1]; for(int i=0;i>a[i][j][k]; } } } cout< 我无法独立完成此题目,此处参考了网上的题解 ### Description: > 关于整数的 2 元圈乘运算⊙定义为:(X⊙Y)=10 进制整数X 的各位数字之和\*10 进制整 > 数 Y 的最大数字+Y 的最小数字。例如,(9⊙30)=9\*3+0=27。 > 对于给定的 10 进制整数X 和 K,由 X 和⊙运算可以组成各种不同的表达式。试设计一 > 个算法,计算出由 X 和 ⊙ 运算组成的值为 K 的表达式最少需用多少个 ⊙ 运算。 > The binary circle multiplication operation ⊙ on integers is defined as follows: (X⊙Y) = the > sum of the digits of the decimal integer X multiplied by the maximum digit of the decimal integer > Y, plus the minimum digit of Y. For example, (9⊙30) = 9*3 + 0 = 27. > For given decimal integers X and K, various expressions can be formed using X and the ⊙ > operation. Design an algorithm to calculate the minimum number of ⊙ operations needed to obtain > an expression composed of X and the ⊙ operation with a value of K. ### Solution: ```c++ #include #include #include #define i32 int #define i64 long long #define u128 unsigned long long #define u32 unsigned int using namespace std; const i32 MAXN = (int) 5e2; // const i64 M = 1e10; const auto L = 0x3f; i64 G[MAXN]; i64 X, K; i64 f (i64 X, i64 Y) {//X@Y运算 i64 numX = 0, mxY = 0, mnY = 10; while (X) { numX += X % 10; X /= 10; } while (Y != 0) { mxY = max (mxY, Y % 10); mnY = min (mnY, Y % 10); Y /= 10; } return numX * mxY + mnY; } i64 get_lim (i64 X, i64 K) {//获取上界 auto num = 0; while (X) { X /= 10; num++; } i64 lim = max (num, 2) * 81 + 9; return max(lim, K); } int main () { memset (G, L, sizeof (G)); cin >> X >> K; auto lim = get_lim (X, K); G[f (X, X)] = 1; G[X] = 0; if (X > K) { goto error; } for (auto i = X; i <= lim; ++i) { for (auto j = X; j <= lim; ++j) { if (G[i] < L && G[j] < L && G[f (i, j)] > G[i] + G[j] + 1) {//i@j到S的运算次数更少,则更新 G[f (i, j)] = G[i] + G[j] + 1; } } } if (G[K] < L) { cout << G[K] << endl; // for (auto p = X; p <= K; ++p) { // cout << p << ":" << G[p] << " "; // } // cout << endl; } else { error: cout << "error" << endl; } return 0; } ```