潍坊一中 2023 秋提高级友好学校赛前联测 1 T3
Mystia 的居酒屋
题目背景
小麻雀 Mystia 开了一间居酒屋,每天清晨她都要跨过门前的河流去收集食材。
题目描述
Mystia 想搭一座跨过河的桥,来方便她取得食材。
河是一条无限长的宽度为 \(W\) 的直线,所有在坐标系中符合 \(0≤y≤W\) 的点都属于这条河流。
河面上有 \(N\) 个木桩,附近的杂货店可以买到 \(M\) 种可以使用的木头圆盘,第 \(k\) 个木桩的坐标为 \((X_k,Y_k)\)。第 \(k\) 种圆盘半径为 \(R_k\),每一块的价格为 \(C_k\)。
她可以买任意种任意多的圆盘,把它们放到河面上。每一个圆盘的中心都必须为某一个木桩的位置。而且,某些圆盘的一部分可以在地面上,即 \(y<0\) 或 \(y>W\)。
Mystia 只能在河两岸或圆盘上移动,两岸即直线 \(y=0\) 或直线 \(y=W\),也可以从一个位置移动到另一个与其相切或相交的圆盘。
请问她修建一座可以从直线 \(y=0\) 到直线 \(y=W\) 走过去的圆盘桥的最小花费是多少?
输入格式
第一行一个整数 \(T\) ,代表测试数据的组数。
对于每组数据:
第一行有三个空格隔开的正整数 \(N,M,W\)。
接下来 \(N\) 行,每行两个空格隔开的自然数 \(X_k,Y_k\)。
接下来 \(M\) 行,每行两个空格隔开的正整数 \(R_k,C_k\)。输出格式
对于每组数据,输出从直线 \(y=0\) 到直线 \(y=W\) 修建一座可以走过去的桥最少的花费。如果这是不可能完成的,那么输出
impossib1e
。样例 #1
样例输入 #1
4 1 1 10 1 5 5 5 11 4 13 19 10 8 7 11 4 26 1 4 2 15 4 19 4 1 9 4 6 19 5 15 10 2 1 3 100 4 10000 5 1000000 11 4 13 19 10 8 7 11 4 26 1 4 2 15 4 19 4 1 9 4 6 19 5 15 10 2 1 3 2 4 3 5 4 1 1 1000000 0 500000 1 1
样例输出 #1
5 206 5 impossib1e
提示
样例解释
对于样例中的第一组数据,如图放置圆盘,刚好可以从河边到达对岸。
对于样例中的第二、三组数据,如图所示,可以修建一座桥。两组数据仅花费不同。
对于样例中的第四组数据,显然无法修建符合题意的桥。
数据范围
每个测试点的具体限制见下表:
测试点编号 \(T≤\) \(N≤\) \(M≤\) \(W,X_k≤\) \(R_k≤\) \(C_k≤\) 特殊性质 \(1\sim2\) \(1\) \(1\) \(1\) \(10^3\) \(10^3\) \(10^3\) 是 \(3\sim5\) \(10\) \(7\) \(7\) \(10^4\) \(10^4\) \(10^4\) 否 \(6\sim8\) \(10\) \(40\) \(40\) \(10^4\) \(10^4\) \(10^4\) 是 \(9\sim12\) \(10\) \(40\) \(40\) \(10^4\) \(10^4\) \(10^4\) 否 \(13\sim20\) \(10\) \(100\) \(100\) \(10^6\) \(10^6\) \(10^6\) 否 \(21\sim25\) \(10\) \(100\) \(20100\) \(10^6\) \(200\) \(200\) 否 特殊性质:对于每组测试数据,所有的 \(X_k\) 均相同。
所有数据保证全部木桩均在河流中,即 \(0≤Y_k≤W\)。
先说一下比较朴素的做法,前 20 个点显然分层图跑最短路,发现 21 ~ 25 \(R_k,C_k \leq 200\) ,我们可以把又小又贵的盘子去掉,然后盘子数量就 \(\leq 200\) 直接跑即可,这么做点数 \(O(nm)\) ,边数 \(O(n^2m^2)\) ,但我隐式建图赛时竟然过掉了,其实这么做复杂度是不太对的
考虑优化一下,我们发现复杂度瓶颈在于边数过大,考虑优化建图。我们可以先连一条默认可以使用到的最小盘子,然后如果想换盘子可以在分层图上下两层同一节点上连一条长为 \(c_x - c_y\) 的边来做到换盘子,边数就变成了 \(O(n^2m)\) 的了,直接跑 dijkstra 就可以过
但这个题有 \(O(n^2m)\) 做法。考虑不拆点设 \(f_{i,j}\) 为 \(i\) 上盘子种类为 \(j\) 情况下 \(1 \rightarrow i\) 的距离,那么考虑模拟dijkstra 的过程。记 \(g_i = \min_j f_{i,j}\) ,原问题改为每次选 \(g_i\) 最小的点用它的所有 \(f_{i,j}\) 更新其他所有的 \(f_{k,l}\) ,更新的时候直接双指针可以做到 \(O(m)\) ,做法即枚举转移到 \(v\) 点时使用的盘子 \(r\) ,则 \(u\) 点使用能使用的最小的盘子 \(l\) 。但是这个最短路看起来是不对的,考虑证明,如果松弛顺序有问题只可能因为两个桩子 \(i,j\) ,当前 \(g_i < g_j\) ,且 \(i,j\) 都在最短路上,且 \(i\) 比 \(j\) 靠后;但此时绕道走 \(j\) 唯一目的只能让 \(i\) 换一个更大的盘子,那么如果其盘子由 \(x\) 换到 \(y\) ,直接更改 \(i\) 的盘子只有 \(f_{i,x} - c_x + c_y\) 。而过去至少要更新到 \(g_j + c_x\) 。于是此时一定不会让 \(j\) 去松弛 \(i\) ,更新顺序就是对的了
听起来好像还是有点难以理解,放一下别人的代码:
int ord[N];
for(int _=1;_<=n;_++){
int u=1;
for(int i=1;i<=n;i++){
if(vis[u]||!vis[i]&&f[i][0]<f[u][0])u=i;
}
ord[u]=_;
vis[u]=true;
for(int i=1;i<=n;i++){
ll d=pow2(ps[i].y-ps[u].y)+pow2(ps[i].x-ps[u].x);
int l=m,v=f[u][m],mv=inf;
for(int j=1;j<=m;j++){
while(l>=2&&pow2(cs[j].r+cs[l-1].r)>=d)l--,v=min(v,f[u][l]);
if(pow2(cs[j].r+cs[l].r)<d)continue;
f[i][j]=min(f[i][j],cs[j].c+v);
f[i][j]=min(f[i][j],mv+cs[j].c);
f[i][0]=min(f[i][0],f[i][j]);
mv=min(mv,f[i][j]-cs[j].c); // 这里是为了计算使用更大的盘子的情况
}
}
}