斜率优化建图学习笔记 & JZOJ 地壳运动题解
本章学习斜率优化建图
请放心食用
引言
- 最小生成树(\(mst\)) (\(Algorithm: \text {Prim or Kruskal}\))
- 从裸题到一丁点技巧,再到丧心病狂的神仙题
- 原始时间复杂度 \(O(N^2)\) 与 \(O(M log M)\)
- 永远的 \(TLE\) ······
正话
本文第一句就是正话
像多次查询而你非得(无奈)每次重新建图,跑一边 \(mst\) 的题
很多时候带有技巧性,优先考虑建图的优化
如果熟悉斜率优化 \(dp\) 的话,像一些边有多种信息会产生贡献,如二维最小生成树(边有两条信息会在不同时候产生不一样的贡献)
可以考虑斜率那些事儿
配上例题,更好食用
地壳运动(mst)
\(Description\)
JZ是一个坐落在地壳运动活跃的山区的城市,常受地质灾害的袭击。
城市中建立了N个应急避难所以躲避灾害,这些避难所从1~N编号。此外有M条道路连接这些避难所,所有避难所间均可通过这M条道路直接或间接到达。由于是在规划良好的市区,道路可以由若干个平行于x或y坐标轴的线段组成,所以避难所xi和yi之间的道路可以用(ui,vi)来表示,道路的长度为ui+vi。由于地壳运动会导致地面拉伸或收缩,可用两个实数k1,k2描述城市的伸缩程度,此时某条道路的实际长度变为 \(ui\times k1+vi\times k2\)。有若干个独立的询问,每次询问给出k1和k2,政府都希望在此地壳运动前提下,以最小的花费维护其中一些道路,使得只用这些被维护的道路仍能使所有避难所间相互连通。因为花费与道路的实际总长成正比,所以你需要对每一次询问求出被维护道路的最短实际总长。
\(Input\)
第一行三个整数N,M,Q,分别表示避难所数量、道路数量、询问数量。
接下来M行每行四个整数xi,yi,ui,vi。xi,yi表示道路连接的两个避难所编号,ui,vi意义如上文所述。
最后Q行每行两个实数,表示每次询问的的k1和k2。
\(Output\)
输出Q行,每行一个实数,表示实际总长,保留三位小数。
\(Sample Input\)
4 8 3
2 1 3 6
3 2 0 7
4 1 7 0
1 4 4 6
2 1 2 7
1 2 2 10
2 2 5 5
4 4 8 9
0.626436771146 0.472537839745
0.977631137354 0.190235819672
0.418883351791 0.221987861358
\(Sample Output\)
12.253
9.671
6.878
解法
- \(30\) 分,依题暴力,每次重新建图,重跑 \(mst\)
- \(60\) 分,用脑袋想想,去掉那些无用的边,捡一些可能的较好边,然后愉快拿到全场第二高分(\(sorry\) , 有初一A掉的)
- \(100\) 分,多样做法,下面介绍一种
依题有两块地区的花费 \(g(x,y) = u_i * k1 + v_i * k2\)
这让我们无法挑取好边,导致每次都要重新建图
倘若我们把它当成一次函数
化简它 \(v_i = -\frac{k1}{k2} u_i + \frac{g(x,y)}{k2}\)
你问我为何要化成这样,我只好回答你
1.参考其他人的解法
2.经验(虽然我没经验)
3.依题
前两点无可反驳,第三点怎个依题,愿闻其详
好,我们要找决策集合,所以一次函数的 \(y\) 与 \(x\) 要和 \(\text{u_i v_i}\) 相关,而两地的实际长度由 \(\text{k1 k2}\) 决定
也就是说靠 \(\text{k1 k2}\) 来确定一条直线
就很自然想到拿这两个东西来当斜率或与截距相关
而本题要在两点间取最优边,基本套路就是用若干直线切决策点,最小化截距
因为 \(k1 > 0 , k2 > 0\) 所以最小化截距就是最小化 \(g(x,y)\)
达到目的,看具体实现
既然要最小化截距,那么我们就要维护下凸壳,上凸的绝对没有下凸的优(自己画一下)
所以我们就有了一点思绪
对于 \((x,y)\) 这两地,我们需寻找其中的最优决策
而我们将在它的决策集合\(\texttt{u_i v_i}\)中挑选
基本会想到先将\(\texttt{u_i v_i}\)看成在平面直角坐标系中的一个点
然后配合化简的式子分析:\(v_i = -\frac{k1}{k2} u_i + \frac{g(x,y)}{k2}\)
先前提到最小化截距
那么我们将决策集合的点先按 \(u_i\) 从小到大排序
然后单调队列维护下凸壳,也就是维护斜率单调递增
但尽管如此,我们的决策点仍旧是太多了,还是会超时
然后我们想想,如果斜率单调递增,那么对于斜率小于直线斜率的点对 \((x_1 , y_1)\) 和 \((x_2 , y_2)\)
因为排过序了,所以有 \(x_1 < x_2\)
这两点你画画图,用直线去切,不难发现
\((x_1 , y_1)\) 和 \((x_2 , y_2)\),后者更优(此时的直线斜率更小)
这样的话,我们凭此弹掉队首的劣点,就能确定最优的点是谁了,直接 \(O(1)\) 取队首
但前提是斜率单调递增
把 \(-\frac{k1}{k2}\) 排个序就好了,升序
具体来说,就是
- 维护每两个地区下凸壳
- 排序\(-\frac{k1}{k2}\)
- 每两个地区重新选边,弹去队首劣点,取新的队首作为最优决策
- 跑 \(Prim\),算答案
代码(仍需开O)
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 35 , Q = 200000;
const double INF = 1e18;
int n , m , q , p[N + 5][N + 5];
double ans[Q + 5] , dis[N + 5] , vis[N + 5] , g[N + 5][N + 5];
struct que{
int id;
double k1 , k2 , k;
}ask[Q + 5];
struct point{
int x , y;
};
vector<point> vec[N + 5][N + 5] , e[N + 5][N + 5];
inline bool cmp(point x , point y) {return x.x < y.x;}
inline bool cmp1(que x , que y) {return x.k < y.k;}
inline void choose(que l)
{
for(register int i = 1; i <= n; i++)
for(register int j = 1; j <= n; j++)
g[i][j] = INF;
for(register int i = 1; i <= n; i++)
for(register int j = i + 1; j <= n; j++)
if (e[i][j].size())
{
if (e[i][j].size() - p[i][j] == 1) g[i][j] = 1.0 * e[i][j][p[i][j]].x * l.k1 + 1.0 * e[i][j][p[i][j]].y * l.k2;
else {
while (p[i][j] < e[i][j].size() - 1 && 1.0 * (e[i][j][p[i][j]].y - e[i][j][p[i][j] + 1].y) / (e[i][j][p[i][j]].x - e[i][j][p[i][j] + 1].x) < l.k)
p[i][j]++;
g[i][j] = 1.0 * e[i][j][p[i][j]].x * l.k1 + 1.0 * e[i][j][p[i][j]].y * l.k2;;
}
g[j][i] = g[i][j];
}
}
inline double prim()
{
for(register int i = 0; i <= n; i++) dis[i] = INF , vis[i] = 0;
int k;
double res = 0;
dis[1] = 0;
for(register int i = 1; i <= n; i++)
{
k = 0;
for(register int j = 1; j <= n; j++)
if (!vis[j] && dis[j] < dis[k]) k = j;
vis[k] = 1 , res += dis[k];
for(register int j = 1; j <= n; j++)
if (!vis[j] && dis[j] > g[k][j]) dis[j] = g[k][j];
}
return res;
}
int main()
{
// freopen("mst.in" , "r" , stdin);
// freopen("mst.out" , "w" , stdout);
scanf("%d%d%d" , &n , &m , &q);
int x , y , u , v , s;
for(register int i = 1; i <= m; i++)
{
scanf("%d%d%d%d" , &x , &y , &u , &v);
if (x == y) continue;
if (x > y) swap(x , y);
vec[x][y].push_back((point){u , v});
}
for(register int i = 1; i <= n; i++)
for(register int j = i + 1; j <= n; j++)
if (vec[i][j].size())
{
sort(vec[i][j].begin() , vec[i][j].end() , cmp);
s = 0 , e[i][j].push_back(vec[i][j][0]);
for(register int k = 1; k < vec[i][j].size(); k++)
{
if (vec[i][j][k].x == e[i][j][s].x)
{
if (vec[i][j][k].y >= e[i][j][s].y) continue;
else e[i][j].pop_back() , s--;
}
if (s == 0) s++ , e[i][j].push_back(vec[i][j][k]);
else {
while (s > 0 && 1.0 * (e[i][j][s].y - e[i][j][s - 1].y) / (e[i][j][s].x - e[i][j][s - 1].x) > 1.0 * (vec[i][j][k].y - e[i][j][s].y) / (vec[i][j][k].x - e[i][j][s].x))
e[i][j].pop_back() , s--;
s++ , e[i][j].push_back(vec[i][j][k]);
}
}
}
double k1 , k2;
for(register int i = 1; i <= q; i++)
{
scanf("%lf%lf" , &k1 , &k2);
ask[i].id = i , ask[i].k1 = k1 , ask[i].k2 = k2 , ask[i].k = -1.0 * k1 / k2;
}
sort(ask + 1 , ask + q + 1 , cmp1);
for(register int i = 1; i <= q; i++)
choose(ask[i]) , ans[ask[i].id] = prim();
for(register int i = 1; i <= q; i++) printf("%.3lf\n" , ans[i]);
}
函数是很美妙的,关于斜率高深莫测的那些事儿,还得慢慢摸索
斜率优化建图学习笔记 & JZOJ 地壳运动题解的更多相关文章
- [斜率优化DP]【学习笔记】【更新中】
参考资料: 1.元旦集训的课件已经很好了 http://files.cnblogs.com/files/candy99/dp.pdf 2.http://www.cnblogs.com/MashiroS ...
- [十二省联考2019]字符串问题——后缀自动机+parent树优化建图+拓扑序DP+倍增
题目链接: [十二省联考2019]字符串问题 首先考虑最暴力的做法就是对于每个$B$串存一下它是哪些$A$串的前缀,然后按每组支配关系连边,做一遍拓扑序DP即可. 但即使忽略判断前缀的时间,光是连边的 ...
- [SDOI2017]天才黑客[最短路、前缀优化建图]
题意 一个 \(n\) 点 \(m\) 边的有向图,还有一棵 \(k\) 个节点的 trie ,每条边上有一个字符串,可以用 trie 的根到某个节点的路径来表示.每经过一条边,当前携带的字符串就会变 ...
- BZOJ5017 [SNOI2017]炸弹 - 线段树优化建图+Tarjan
Solution 一个点向一个区间内的所有点连边, 可以用线段树优化建图来优化 : 前置技能传送门 然后就得到一个有向图, 一个联通块内的炸弹可以互相引爆, 所以进行缩点变成$DAG$ 然后拓扑排序. ...
- 【LibreOJ】#6354. 「CodePlus 2018 4 月赛」最短路 异或优化建图+Dijkstra
[题目]#6354. 「CodePlus 2018 4 月赛」最短路 [题意]给定n个点,m条带权有向边,任意两个点i和j还可以花费(i xor j)*C到达(C是给定的常数),求A到B的最短距离.\ ...
- CF1007D. Ants(树链剖分+线段树+2-SAT及前缀优化建图)
题目链接 https://codeforces.com/problemset/problem/1007/D 题解 其实这道题本身还是挺简单的,这里只是记录一下 2-SAT 的前缀优化建图的相关内容. ...
- 【BZOJ3681】Arietta 树链剖分+可持久化线段树优化建图+网络流
[BZOJ3681]Arietta Description Arietta 的命运与她的妹妹不同,在她的妹妹已经走进学院的时候,她仍然留在山村中.但是她从未停止过和恋人 Velding 的书信往来.一 ...
- 洛谷P3783 [SDOI2017]天才黑客(前后缀优化建图+虚树+最短路)
题面 传送门 题解 去看\(shadowice\)巨巨写得前后缀优化建图吧 话说我似乎连线段树优化建图的做法都不会 //minamoto #include<bits/stdc++.h> # ...
- 【ARC069F】Flags 2-sat+线段树优化建图+二分
Description 数轴上有 n 个旗子,第 ii 个可以插在坐标 xi或者 yi,最大化两两旗子之间的最小距离. Input 第一行一个整数 N. 接下来 N 行每行两个整数 xi, ...
- 【bzoj5017】[Snoi2017]炸弹 线段树优化建图+Tarjan+拓扑排序
题目描述 在一条直线上有 N 个炸弹,每个炸弹的坐标是 Xi,爆炸半径是 Ri,当一个炸弹爆炸时,如果另一个炸弹所在位置 Xj 满足: Xi−Ri≤Xj≤Xi+Ri,那么,该炸弹也会被引爆. 现在 ...
随机推荐
- vue3 watch笔记
watchEffect 执行传入的一个函数,同时自动追踪函数中依赖到的数据,并在其依赖变更时重新运行该函数. 并且会在 组件挂载前 立即调用一次,(默认是挂载前,可通过修改 flush 属性改变,后边 ...
- 基于EasyCode定制Mybatisplus全自动单表实现:新增/批量新增/修改/批量删除/分页查询/ID查询
基于EasyCode定制Mybatisplus全自动单表实现CRUD接口 分页查询 ID查询 新增 批量新增 修改 批量删除 注意使用了MybatisPlus的自动填充功能,和insertBatchS ...
- [OpenCV实战]52 在OpenCV中使用颜色直方图
颜色直方图是一种常见的图像特征,顾名思义颜色直方图就是用来反映图像颜色组成分布的直方图.颜色直方图的横轴表示像素值或像素值范围,纵轴表示该像素值范围内像素点的个数或出现频率.颜色直方图属于计算机视觉中 ...
- css、js 缓存清除
此种方式完美达到了清除缓存的效果 css引入标签可在一个引号内完成,js引入标签由于解析原因需要将两个标签拆开再组 使用:按照此格式,放在原 <link /> 或 <script&g ...
- [深度探索C++对象模型]memcpy和memset注意事项
不管使用memcpy()还是memset(),都只有在"classes不含任何由编译器产生的内部members"时才能运行.如果这个类声明一个或者一个以上的virtual func ...
- Qt界面设计--侧边栏隐藏和滑出
在日常项目中,界面布局上经常使用到侧边栏的方式,在侧边栏放置控件进行复合使用,可以实现子功能界面的隐藏和滑出,效果展示如下: 界面控件很简单,主界面QWidget,侧边栏也用一个QWidget和一个按 ...
- Hugging Face 开源库介绍
Hugging Face 的开源生态今年成长迅速,timm 成为新加入的成员.diffusers.evaluate 以及 skops 等各种库蓬勃发展. Transformers Transforme ...
- 玩转web3第一篇——web3-react
概况 web3-react是由Noah Zinsmeister开发的一个web3框架,主要功能是实时获取DApp里的关键数据(如用户当前连接的地址.网络.余额等). Noah也是著名的去中心化交易所u ...
- 1.31 wlx 魔怔 9 解法交互题小结
参考题解地址 1. 从树上任意一个节点开始,跳到其随机一个后代,跳到叶子的期望次数为 \(H_{siz_u}=\ln(siz_u)\). 证明: 首先考虑一条链的情况.设在第 \(i\) 个点期望次数 ...
- 创建型模式 - 建造者模式Builder
学习而来,代码是自己敲的.也有些自己的理解在里边,有问题希望大家指出. 最近这个脑T---瘫----CSDN有毛病吧?我写这个都是涉及到广告了?谁家的广告? 谁给我钱了?审核策略设计者是什么NTR? ...