简化の题目:
求 \(\sum\limits_{i=1}^n{\min\left\{|a_i-b_i|,|a_i|+|y-b_i|\right\}}\) 的最小值
思路
对每一个 \(y\) ,考虑 \(O(1)\) 计算答案。
我们先计算出 \(\sum|a_i-b_i|\) ,把答案转换成“节省”的最大值。
对于一组 \(a_i,b_i\) ,如果其经过传送门,那么传送门的终点一定在以 \(b_i\) 为中心点的一段区间上。同时,能节省的路程以 \(b_i\) 为中心向两边减少,也就是这样:
接下来,我们只要求出 \(b_i\) 处节省了多少,就能求出左端点和右端点。
有两种情况
-
\(|a_i|\ge|a_i-b_i|\) 没有节省,去到传送门的距离大于等于两点距离。
-
\(|a_i|<|a_i-b_i|\) 节省了 \(|a_i-b_i|-|a_i|\) 即两点距离减去到传送门的距离。
如果是第二种情况,那么左端点就为:\(b_i-|a_i-b_i|+|a_i|\)
右端点为:\(b_i+|a_i-b_i|-|a|\)
就是中心点加减节省了多少
USACO的题解分成了两种情况,其实就是把绝对值拆开了。
得到左中右端点之后,把左端点的斜率减1,中间点的斜率加2(想想为什么),右端点的斜率减1。统计答案时用差分的方法,得到当前点的“节省值”比上一个点增加了多少,然后统计答案。
实现
在更改斜率时,使用平衡树(map),或离散化解决空间问题。同时,我们发现某个端点一定可以得到答案(感性理解)。所以,我们可以只存端点,用斜率乘距离来差分,即可统计答案。
代码
#include<map>
#include<cmath>
#include<cstdio>
#include<iostream>
using namespace std;
int n,a,b;
map<int,int>f;
long long ans;
void read(int &x){
char c=getchar();
for(;c<33;c=getchar());
int f=1;
if(c=='-'){
f=-1;
c=getchar();
}
for(x=0;(c>47)&&(c<58);x=x*10+c-48,c=getchar());
x*=f;
}
int main(){
freopen("teleport.in","r",stdin);
freopen("teleport.out","w",stdout);
read(n);
for(int i=1;i<=n;i++){
read(a);read(b);
ans+=abs(a-b);
if(abs(a)<=abs(a-b)){
f[b]+=2;
f[b-abs(a-b)+abs(a)]--;
f[b+abs(a-b)-abs(a)]--;
}
}
long long c=ans,s=0,l=-0x7fffffff;
for(map<int,int>::iterator i=f.begin();i!=f.end();i++){
int y=i->first,fx=i->second;
c+=s*(y-l);
l=y;
s+=fx;
ans=min(ans,c);
}
printf("%lld",ans);
fclose(stdin);
fclose(stdout);
}