为什么这数据能水到可以枚举角度 ac 啊


# 题面

给你 \(n\) 个平面向量 \((x_i,y_i)\),对于每个 \(k=1\sim n\),求「从给出的 \(n\) 个向量中不重复地选择 \(k\) 个,\(k\) 个向量的和的模长最大是多少」。

数据规模:\(n\le1000\)。


# 解析

这种「选择 \(k\) 个」的题目,我们之前往往会从 DP 考虑,或者贪心求解。但是我们发现向量并不满足局部最优就是全局最优。

于是这道题我们换一个思路,不从选的过程考虑,而从选的结果 —— 也就是答案的角度考虑。

如果我们知道了答案为 \(\mathbf{v}\),那么一定是由在 \(\mathbf{v}\) 方向上投影最大的 \(k\) 个向量组成的。于是我们可以尝试「旋转」答案向量的方向,然后贪心地选取向量。

虽然数据水,离散地枚举答案向量角度可以 ac,但是角度毕竟是连续的,这种做法不是很靠谱(但是很难卡掉)。

连续的枚举一般考虑枚举临界点。不妨设我们逆时针旋转答案向量 \(\mathbf{v}\),记 id[i] 表示当前在 \(\mathbf{v}\) 方向上投影从大到小第 \(i\) 个向量是哪一个,同理定义 rnk[i] 表示 \(i\) 向量的排名(rnk[id[i]] = i)。

我们发现只有 id 发生变化 —— 也即两个向量的相对投影大小改变时,答案才会改变。设 \(\mathbf{u,v}\) 为两个方向不同的向量:

于是一对向量会产生两个临界点,总共会有 \(\mathcal{O}(n^2)\) 个临界。将它们极角排序过后逆时针扫一遍。

每经过一个临界点,就会有 rank 相邻的两个向量的 rank 发生交换(记为 rnk, rnk + 1)。扫描时,维护当前 rank,当 rnk, rnk + 1 交换时,只会改变前 rnk 个和前 rnk + 1 个向量的和,\(\mathcal{O}(1)\) 更新答案即可。

唯一的麻烦点是给出的 \(n\) 个向量可能重叠……我的处理是把重叠的向量看成一个,记录一下个数。只能自己意会一下或者看一看代码了。


# 源代码

  1. /*Lucky_Glass*/
  2. #include <cmath>
  3. #include <cstdio>
  4. #include <cstring>
  5. #include <cassert>
  6. #include <iostream>
  7. #include <algorithm>
  8. using namespace std;
  9. typedef long double ldouble;
  10. const int N = 1005;
  11. const ldouble EPS = 1e-12;
  12. #define con(typ) const typ &
  13. #define sec second
  14. #define fir first
  15. template<class typ> typ iAbs(con(typ) key) {return key < 0 ? -key : key;}
  16. template<class typ> int sgn(con(typ) key) {
  17. if ( iAbs(key) <= EPS ) return 0;
  18. return key < 0 ? -1 : 1;
  19. }
  20. struct Vector {
  21. ldouble x, y;
  22. Vector() {}
  23. Vector(con(ldouble) _x, con(ldouble) _y) : x(_x), y(_y) {}
  24. ldouble len() const {return x * x + y * y;}
  25. Vector operator - (con(Vector) p) const {
  26. return Vector(x - p.x, y - p.y);
  27. }
  28. Vector operator + (con(Vector) p) const {
  29. return Vector(x + p.x, y + p.y);
  30. }
  31. friend ldouble dot(con(Vector) p, con(Vector) q) {
  32. return p.x * q.x + p.y * q.y;
  33. }
  34. Vector operator -() const {return Vector(-x, -y);}
  35. bool operator != (con(Vector) p) const {
  36. return sgn(x - p.x) || sgn(y - p.y);
  37. }
  38. bool operator < (con(Vector) p) const {
  39. if ( sgn(x - p.x) ) return sgn(x - p.x) < 0;
  40. return sgn(y - p.y) < 0;
  41. }
  42. Vector cwise90() const {return Vector(y, -x);}
  43. } sum[N];
  44. struct Data {
  45. Vector v; int cnt;
  46. Data() {}
  47. Data(con(Vector) _v, con(int) _c) : v(_v), cnt(_c) {}
  48. } dat[N];
  49. int nn, n, ndv;
  50. pair<int, int> inp[N];
  51. int cnt[N], rnk[N];
  52. ldouble ans[N];
  53. struct Divi {
  54. int a, b;
  55. ldouble ang;
  56. Divi() {}
  57. Divi(con(int) _a, con(int) _b, con(ldouble) _ang)
  58. : a(_a), b(_b), ang(_ang) {}
  59. bool operator == (con(Divi) p) const {return !sgn(ang - p.ang);}
  60. static bool cmpAng(con(Divi) p, con(Divi) q) {return sgn(p.ang - q.ang) < 0;}
  61. static bool cmpID(con(Divi) p, con(Divi) q) {
  62. if ( rnk[p.a] != rnk[q.a] ) return rnk[p.a] < rnk[q.a];
  63. return rnk[p.b] < rnk[q.b];
  64. }
  65. } dv[N * N];
  66. void init() {
  67. sum[0] = Vector(0, 0);
  68. for (int i = 1, tmp = 0; i <= n; i++) {
  69. for (int j = 1; j <= dat[i].cnt; j++) {
  70. tmp++;
  71. sum[tmp] = sum[tmp - 1] + dat[i].v;
  72. ans[tmp] = sum[tmp].len();
  73. }
  74. rnk[i] = i, cnt[i] = tmp;
  75. }
  76. }
  77. // q is better than p then
  78. void done(con(int) p, con(int) q) {
  79. // assert( rnk[p] == rnk[q] - 1 );
  80. int tmp = cnt[rnk[p] - 1];
  81. for (int i = 1; i <= dat[q].cnt; i++) {
  82. tmp++;
  83. sum[tmp] = sum[tmp - 1] + dat[q].v;
  84. ans[tmp] = max(ans[tmp], sum[tmp].len());
  85. }
  86. cnt[rnk[p]] = tmp;
  87. for (int i = 1; i <= dat[p].cnt; i++) {
  88. tmp++;
  89. sum[tmp] = sum[tmp - 1] + dat[p].v;
  90. ans[tmp] = max(ans[tmp], sum[tmp].len());
  91. }
  92. swap(rnk[p], rnk[q]);
  93. }
  94. int main() {
  95. scanf("%d", &nn);
  96. for (int i = 1; i <= nn; i++)
  97. scanf("%d%d", &inp[i].fir, &inp[i].sec);
  98. sort(inp + 1, inp + 1 + nn);
  99. for (int i = 1; i <= nn;) {
  100. int j = i;
  101. while ( j <= nn && inp[i] == inp[j] ) j++;
  102. dat[++n] = Data(Vector(inp[i].fir, inp[i].sec), j - i);
  103. inp[n] = inp[i];
  104. i = j;
  105. }
  106. for (int i = 1; i <= n; i++)
  107. for (int j = i + 1; j <= n; j++) {
  108. double k1 = atan2(inp[j].fir - inp[i].fir, inp[i].sec - inp[j].sec),
  109. k2 = atan2(inp[i].fir - inp[j].fir, inp[j].sec - inp[i].sec);
  110. dv[++ndv] = Divi(j, i, k1);
  111. dv[++ndv] = Divi(i, j, k2);
  112. }
  113. sort(dv + 1, dv + 1 + ndv, Divi::cmpAng);
  114. init();
  115. for (int i = 1; i <= ndv; ) {
  116. int j = i;
  117. while ( j <= ndv && dv[i] == dv[j] ) j++;
  118. sort(dv + i, dv + j, Divi::cmpID);
  119. while ( i < j ) {
  120. done(dv[i].a, dv[i].b);
  121. i++;
  122. }
  123. }
  124. for (int i = 1; i <= nn; i++)
  125. printf("%.8f\n", (double)sqrt(ans[i]));
  126. return 0;
  127. }

THE END

Thanks for reading!

日月出矣 爝火不息

时雨降矣 井水犹汲

有情有信 无为无形

逍遥平生意

——《从前有个衔玉教》By 星葵/鲜洋芋/溱绫西陌

> Link 【0412乐正绫诞生祭】从前有个衔玉教-Bilibili

「SOL」E-Lite (Ural Championship 2013)的更多相关文章

  1. loj#2013. 「SCOI2016」幸运数字 点分治/线性基

    题目链接 loj#2013. 「SCOI2016」幸运数字 题解 和树上路径有管...点分治吧 把询问挂到点上 求出重心后,求出重心到每个点路径上的数的线性基 对于重心为lca的合并寻味,否则标记下传 ...

  2. loj #2013. 「SCOI2016」幸运数字

    #2013. 「SCOI2016」幸运数字 题目描述 A 国共有 n nn 座城市,这些城市由 n−1 n - 1n−1 条道路相连,使得任意两座城市可以互达,且路径唯一.每座城市都有一个幸运数字,以 ...

  3. AC日记——「SCOI2016」幸运数字 LiBreOJ 2013

    「SCOI2016」幸运数字 思路: 线性基: 代码: #include <bits/stdc++.h> using namespace std; #define maxn 20005 # ...

  4. FileUpload控件「批次上传 / 多档案同时上传」的范例--以「流水号」产生「变量名称」

    原文出處  http://www.dotblogs.com.tw/mis2000lab/archive/2013/08/19/multiple_fileupload_asp_net_20130819. ...

  5. 一本通1648【例 1】「NOIP2011」计算系数

    1648: [例 1]「NOIP2011」计算系数 时间限制: 1000 ms         内存限制: 524288 KB [题目描述] 给定一个多项式 (ax+by)k ,请求出多项式展开后 x ...

  6. 「SCOI2016」背单词

    「SCOI2016」背单词 Lweb 面对如山的英语单词,陷入了深深的沉思,「我怎么样才能快点学完,然后去玩三国杀呢?」.这时候睿智的凤老师从远处飘来,他送给了 Lweb 一本计划册和一大缸泡椒,然后 ...

  7. loj2009. 「SCOI2015」小凸玩密室

    「SCOI2015」小凸玩密室 小凸和小方相约玩密室逃脱,这个密室是一棵有 $ n $ 个节点的完全二叉树,每个节点有一个灯泡.点亮所有灯泡即可逃出密室.每个灯泡有个权值 $ A_i $,每条边也有个 ...

  8. 「译」JUnit 5 系列:条件测试

    原文地址:http://blog.codefx.org/libraries/junit-5-conditions/ 原文日期:08, May, 2016 译文首发:Linesh 的博客:「译」JUni ...

  9. 「译」JUnit 5 系列:扩展模型(Extension Model)

    原文地址:http://blog.codefx.org/design/architecture/junit-5-extension-model/ 原文日期:11, Apr, 2016 译文首发:Lin ...

  10. JavaScript OOP 之「创建对象」

    工厂模式 工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建具体对象的过程.工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题. function createPers ...

随机推荐

  1. LAN8720 调试笔记

    因为项目需要开始研究LAN8720,还没有仔细研究芯片手册就根据网上能找到的原理图画了第一版电路 调试整整花了我2天半,期间多亏硬汉哥的耐心解答.下面是我调试过程中遇到的一些坑   1.TPTX.TO ...

  2. vs 工具 dumpbin & corflags

    dumpbin 查看 dll 接口函数 > dumpbin /exports "/path/to/dll" dumpbin 查看 exe.dll 依赖的动态库 > du ...

  3. GIT Authentication failed for错误问题处理

    1. Settings ==> Developer settings ==> Personal access tokens ==> Generate new token   生成新的 ...

  4. BundleFusion_Ubuntu_Pangolin 安装的一些error

    /usr/bin/ld: 找不到 -lEigen3::Eigen 解决方法:find_package(Eigen3 REQUIRED)为list(APPEND CMAKE_INCLUDE_PATH & ...

  5. 前端性能测试lighthouse的使用

    lighthouse的安装有两种方式: github地址:https://github.com/GoogleChrome/lighthouse 一.如果可以FQ的话可以从 chrome 扩展插件里直接 ...

  6. MySQL半一致读实验

    参考资料: https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html https://www.y ...

  7. APP学习3

    1. 常见控件 Button控件 继承自TextView控件,既可以显示文本,又可以显示图片,同时也允许用户通过点击来执行操作,点击效果. onClick属性:先在layout文件中指定onClick ...

  8. Hadoop 设置静态IP、关闭防火墙

    设置静态IP [root@localhost ~]# cd /etc[root@localhost etc]# cd sysconfig[root@localhost sysconfig]# cd n ...

  9. https://github.com/wuweilin/springboot-login-demo

    wuweilin/springboot-login-demo: Springboot后端登录注册项目演示demo (github.com) jdk-8u172-windows-x64.exe apac ...

  10. JVM中的GC系统

    什么是GC? GC(Garbage Collection)称之为垃圾回收,在JVM的执行引擎中自带这样的一个GC系统,此系统会按照一定的算法对内存进行监控和垃圾回收. 如何判断哪些对象是垃圾? 1.引 ...