[关闭]
@Dmaxiya 2020-08-24T23:47:02.000000Z 字数 7749 阅读 1211

最长上升子序列 & 几何题解

Hello_World


最长上升子序列

裸题

题目链接

OpenJudge 2757: 最长上升子序列

题意

对于一个序列,求出其最长递增子序列的长度,序列最长为 1000,可以用 解法。

解法

用一个数组 dp 来存放从第一个位置到当前位置的最长上升子序列的长度,于是就有递推公式:


最后从头到尾扫一遍,输出

代码

  1. #include <iostream>
  2. #include <algorithm>
  3. #include <cstring>
  4. #include <string>
  5. #include <vector>
  6. #include <list>
  7. #include <queue>
  8. #include <stack>
  9. #include <map>
  10. #include <set>
  11. #include <cstdio>
  12. #include <cstdlib>
  13. #include <cmath>
  14. #include <climits>
  15. using namespace std;
  16. #define LL long long
  17. const int maxn = 1005;
  18. int num[maxn], dp[maxn];
  19. int n, ans;
  20. int main() {
  21. while(scanf("%d", &n) != EOF) {
  22. // 初始化
  23. ans = 0;
  24. memset(dp, 0, sizeof(dp));
  25. // 输入原序列
  26. for(int i = 0; i < n; ++i) {
  27. scanf("%d", &num[i]);
  28. // 获得dp 数组
  29. for(int j = 0; j < i; ++j) {
  30. if(num[i] > num[j]) {
  31. dp[i] = max(dp[i], dp[j]);
  32. }
  33. }
  34. ++dp[i];
  35. // 查找最大值
  36. ans = max(ans, dp[i]);
  37. }
  38. printf("%d\n", ans);
  39. }
  40. return 0;
  41. }

解法

用一个数组 dp 来将第 个数字 存放在下标为以 结尾的最长上升子序列的长度的位置的最小数字,比如对于序列 ,用 来表示数字 在以 为结尾的最长上升子序列的长度为 ,于是有:,对于相同的 的值,取最小的 ,放置在 上,这样 dp 数组的大小就是最长上升子序列的长度,对于该例子,dp 数组为

现在需要解决的问题是如何得到这个 dp 数组,算法步骤如下:
1. 初始化 dp 数组为空;
2. 从 扫描原序列,对于序列的每一个元素 ,从 dp 数组中查找第一个大于等于 的数字 ;
3. 如果不存在 ,则在 dp 数组的末尾添加 ;
4. 如果存在,则将 替换为 ;
对于如上序列 ,其算法过程如下:

步骤 num 数组 dp 数组
1 1 1
2 1 5 1 5
3 1 5 6 1 5 6
4 1 5 6 4 1 4 6
5 1 5 6 4 3 1 3 6
6 1 5 6 4 3 8 1 3 6 8
7 1 5 6 4 3 8 7 1 3 6 7
8 1 5 6 4 3 8 7 6 1 3 6 7

从以上步骤可以明显地看出,dp 数组始终有序,于是查找 dp 数组中第一个大于等于 的值可以用二分查找,其时间复杂度为 ,其中 为原序列的长度, 为 dp 数组的最大长度,

代码

  1. #include <iostream>
  2. #include <algorithm>
  3. #include <cstring>
  4. #include <string>
  5. #include <vector>
  6. #include <list>
  7. #include <queue>
  8. #include <stack>
  9. #include <map>
  10. #include <set>
  11. #include <cstdio>
  12. #include <cstdlib>
  13. #include <cmath>
  14. #include <climits>
  15. using namespace std;
  16. #define LL long long
  17. const int maxn = 1005;
  18. int num[maxn], dp[maxn];
  19. int n, cnt;
  20. int *iter;
  21. int main() {
  22. while(scanf("%d", &n) != EOF) {
  23. // 初始化
  24. cnt = 0;
  25. memset(dp, 0, sizeof(dp));
  26. // 输入原序列
  27. for(int i = 0; i < n; ++i) {
  28. scanf("%d", &num[i]);
  29. // 更新dp 数组
  30. iter = lower_bound(dp, dp + cnt, num[i]);
  31. *iter = num[i];
  32. if(iter == dp + cnt) {
  33. ++cnt;
  34. }
  35. }
  36. printf("%d\n", cnt);
  37. }
  38. return 0;
  39. }

变体

题目链接

Codeforces 4D: Mysterious Present

题意

将一张卡片放到一张信封中,将一个信封放到更大的信封中,每个信封都有对应的长和宽的值,卡片能放到信封里的条件是卡片的长和宽严格小于信封的长和宽,小信封能放到大信封里的条件是小信封的长和宽严格小于大信封的长和宽,给出卡片的大小,以及所有信封的大小,问将卡片放入信封中,最多能套几层信封,并按信封大小的顺序,输出信封的序号。

最长上升子序列解法

先将信封按照长为主关键字、宽为次关键字从小到大排序,从卡片能放入的最小信封开始, 查找最长上升子序列,并记下能被第 个信封装下的,层数最多的小一号的信封的序号。接着用一个栈或者递归输出信封序号。

代码

  1. #include <iostream>
  2. #include <algorithm>
  3. #include <cstring>
  4. #include <string>
  5. #include <vector>
  6. #include <list>
  7. #include <queue>
  8. #include <stack>
  9. #include <map>
  10. #include <set>
  11. #include <cstdio>
  12. #include <cstdlib>
  13. #include <cmath>
  14. #include <climits>
  15. using namespace std;
  16. #define LL long long
  17. const int maxn = 5005;
  18. struct Node {
  19. int w, h, pos, layer, pre;
  20. };
  21. Node node[maxn];
  22. int n, w, h, cnt, Maxindex;
  23. stack<int> S;
  24. bool operator<(const Node &a, const Node &b) {
  25. if(a.w == b.w) return a.h < b.h;
  26. return a.w < b.w;
  27. }
  28. int main() {
  29. while(scanf("%d%d%d", &n, &w, &h) != EOF) {
  30. // 初始化
  31. cnt = 0;
  32. Maxindex = 0;
  33. memset(node, 0, sizeof(node));
  34. // 读入数据并筛选掉不能装下卡片的信封
  35. for(int i = 1; i <= n; ++i) {
  36. scanf("%d%d", &node[cnt].w, &node[cnt].h);
  37. if(node[cnt].w > w && node[cnt].h > h) {
  38. node[cnt++].pos = i;
  39. }
  40. }
  41. sort(node, node + cnt);
  42. for(int i = 0; i < cnt; ++i) {
  43. // 初始化
  44. node[i].pre = -1;
  45. node[i].layer = 1;
  46. // 获得dp 数组
  47. for(int j = 0; j < i; ++j) {
  48. if(node[j].w < node[i].w &&
  49. node[j].h < node[i].h &&
  50. node[j].layer + 1 > node[i].layer) {
  51. node[i].pre = j;
  52. node[i].layer = node[j].layer + 1;
  53. }
  54. }
  55. // 记录最大层数信封下标
  56. if(node[Maxindex].layer < node[i].layer) {
  57. Maxindex = i;
  58. }
  59. }
  60. if(cnt != 0) {
  61. // 获取各信封下标
  62. while(Maxindex != -1) {
  63. S.push(Maxindex);
  64. Maxindex = node[Maxindex].pre;
  65. }
  66. // 输出信封序号
  67. printf("%d\n", S.size());
  68. while(!S.empty()) {
  69. printf("%d ", node[S.top()].pos);
  70. S.pop();
  71. }
  72. printf("\n");
  73. } else {
  74. printf("0\n");
  75. }
  76. }
  77. return 0;
  78. }

拓扑排序解法

先将给出的信封按照长、宽的主、次关键字排序,然后 预处理出一张图:如果信封 能够装入信封 ,那么就在图上添加一条从 指向 的边,这样形成一个有向无环图,接着进行拓扑排序,排序过程中,每有一个点出队,就查找该点的上一节点的最大信封层数,拓扑排序过程如下:
1. 初始化一个空队列,计算每个节点的入度
2. 将所有入度为 的节点加入队列
3. 不断从队列中弹出元素,直到队列为空,每弹出一个元素,就进行以下操作:
① 查找该节点指向的所有点,将它们的入度减一;
② 入度减一后,如果某个点的入度为 ,则将该节点入队;
③ 查找指向该节点的所有节点的最大信封层数;
④ 该节点的信封层数即最大信封层数加一;
⑤ 如果该节点的最大信封层数大于全局最大信封层数,记录该下标

代码

  1. #include <iostream>
  2. #include <algorithm>
  3. #include <cstring>
  4. #include <string>
  5. #include <vector>
  6. #include <list>
  7. #include <queue>
  8. #include <stack>
  9. #include <map>
  10. #include <set>
  11. #include <cstdio>
  12. #include <cstdlib>
  13. #include <cmath>
  14. #include <cfloat>
  15. #include <climits>
  16. using namespace std;
  17. #define LL long long
  18. const int maxn = 5005;
  19. struct Node {
  20. int w, h, pos, pre, layer;
  21. };
  22. Node node[maxn];
  23. int tmp;
  24. bool G[maxn][maxn];
  25. stack<int> S;
  26. queue<int> Q;
  27. int n, w, h, cnt, Maxindex;
  28. int deg[maxn];
  29. int x;
  30. bool operator<(const Node &a, const Node &b) {
  31. if(a.w == b.w) return a.h < b.h;
  32. return a.w < b.w;
  33. }
  34. int main() {
  35. while(scanf("%d%d%d", &n, &w, &h) != EOF) {
  36. // 初始化
  37. cnt = 0;
  38. Maxindex = 0;
  39. memset(node, 0, sizeof(node));
  40. memset(G, 0, sizeof(G));
  41. for(int i = 0; i < n; ++i) {
  42. node[i].pre = -1;
  43. node[i].layer = 1;
  44. }
  45. // 读入数据
  46. for(int i = 1; i <= n; ++i) {
  47. scanf("%d%d", &node[cnt].w, &node[cnt].h);
  48. if(node[cnt].w > w && node[cnt].h > h) {
  49. node[cnt++].pos = i;
  50. }
  51. }
  52. sort(node, node + cnt);
  53. // 构图如果小信封i 能放入大信封j内容内,则i 有一条指向j 的边
  54. for(int i = 0; i < cnt; ++i) {
  55. for(int j = 0; j < i; ++j) {
  56. if(node[j].w < node[i].w &&
  57. node[j].h < node[i].h) {
  58. G[j][i] = true;
  59. ++deg[i];
  60. }
  61. }
  62. }
  63. // 初始化队列
  64. for(int i = 0; i < cnt; ++i) {
  65. if(deg[i] == 0) {
  66. Q.push(i);
  67. }
  68. }
  69. // 拓扑排序
  70. while(!Q.empty()) {
  71. tmp = Q.front();
  72. Q.pop();
  73. // 从图中“删去”点 tmp,“删除”节点tmp 指向的所有点的边
  74. for(int i = 0; i < cnt; ++i) {
  75. if(G[tmp][i]) {
  76. --deg[i];
  77. if(deg[i] == 0) {
  78. Q.push(i);
  79. }
  80. }
  81. }
  82. // 从图中查找指向点tmp 的所有的点中
  83. // 能包含层数最多的小一号信封层数
  84. for(int i = 0; i < cnt; ++i) {
  85. if(G[i][tmp] && node[tmp].layer < node[i].layer + 1) {
  86. node[tmp].layer = node[i].layer + 1;
  87. node[tmp].pre = i;
  88. }
  89. }
  90. // 查找全局最大层数的信封下标
  91. if(node[Maxindex].layer < node[tmp].layer) {
  92. Maxindex = tmp;
  93. }
  94. }
  95. if(cnt == 0) {
  96. printf("0\n");
  97. } else {
  98. // 获取各信封下标
  99. while(Maxindex != -1) {
  100. S.push(Maxindex);
  101. Maxindex = node[Maxindex].pre;
  102. }
  103. // 输出信封序号
  104. printf("%d\n", S.size());
  105. while(!S.empty()) {
  106. printf("%d ", node[S.top()].pos);
  107. S.pop();
  108. }
  109. printf("\n");
  110. }
  111. }
  112. return 0;
  113. }

几何

题目链接

Codeforces 1C: Ancient Berland Circus

题意

给三个顶点的坐标,求包含这三个顶点的面积最小的正多边形面积

题解

先做三角形的外接圆,求三个圆心角的最大公约数,这个最大公约数就是所求正多边形外接圆的内角,将内角对应三角形的面积乘上多边形的边数,就是所求多边形的面积,本题数据精度取 可以过,取 反而过不了。

公式推导:
设三角形外接圆圆心为 ,则:


化简得:

令:

则:

解得:

对于每个点的坐标,求出对应的倾斜角(注意程序中 函数在 中的返回值):

对倾斜角排序后求出三边夹角 ,求出三个圆心角的最大公约数 ,有:

代码

  1. #include <iostream>
  2. #include <algorithm>
  3. #include <cstring>
  4. #include <string>
  5. #include <vector>
  6. #include <list>
  7. #include <queue>
  8. #include <stack>
  9. #include <map>
  10. #include <set>
  11. #include <cstdio>
  12. #include <cstdlib>
  13. #include <cmath>
  14. #include <cfloat>
  15. #include <climits>
  16. using namespace std;
  17. #define LL long long
  18. const double PI = acos(-1);
  19. const double eps = 1e-3;
  20. // 在精度为eps 时,判断duoble 型数据是否为0
  21. bool zero(double x) {
  22. return fabs(x) < eps;
  23. }
  24. // 计算double 型数值的gcd
  25. double gcd(double x, double y) {
  26. if(zero(y)) {
  27. return x;
  28. } else {
  29. return gcd(y, fmod(x, y));
  30. }
  31. }
  32. // 计算倾斜角
  33. double Get_deg(double x, double y) {
  34. if(zero(x)) {
  35. if(y > eps)
  36. return PI / 2;
  37. else
  38. return 1.5 * PI;
  39. }
  40. if(x < 0) {
  41. return atan(y / x) + PI;
  42. } else if(x > 0 && y < 0) {
  43. return atan(y / x) + 2 * PI;
  44. }
  45. return atan(y / x);
  46. }
  47. int main() {
  48. double x1, y1, x2, y2, x3, y3;
  49. scanf("%lf%lf%lf%lf%lf%lf", &x1, &y1, &x2, &y2, &x3, &y3);
  50. double ax1 = 2 * (x1 - x2);
  51. double ay1 = 2 * (y1 - y2);
  52. double b1 = x1 * x1 - x2 * x2 + y1 * y1 - y2 * y2;
  53. double ax2 = 2 * (x1 - x3);
  54. double ay2 = 2 * (y1 - y3);
  55. double b2 = x1 * x1 - x3 * x3 + y1 * y1 - y3 * y3;
  56. double D = ax1 * ay2 - ay1 * ax2;
  57. double D1 = b1 * ay2 - ay1 * b2;
  58. double D2 = ax1 * b2 - b1 * ax2;
  59. double x = D1 / D;
  60. double y = D2 / D;
  61. double r = sqrt((x1 - x) * (x1 - x) + (y1 - y) * (y1 - y));
  62. double deg[3];
  63. deg[0] = Get_deg(x1 - x, y1 - y);
  64. deg[1] = Get_deg(x2 - x, y2 - y);
  65. deg[2] = Get_deg(x3 - x, y3 - y);
  66. sort(deg, deg + 3);
  67. double Deg1 = 2 * PI - deg[2] + deg[0];
  68. double Deg2 = deg[1] - deg[0];
  69. double Deg3 = deg[2] - deg[1];
  70. double g = gcd(Deg1, Deg2);
  71. g = gcd(g, Deg3);
  72. printf("%.8lf\n", r * r * sin(g) * PI / g);
  73. return 0;
  74. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注