P6246 [IOI2000] 邮局 加强版 加强版 题解
KobeBeanBryantCox · · 题解
P6246 [IOI2000] 邮局 加强版 加强版 题解
题目传送门。
我这个题做了 5 个小时 15 分钟,其中 4 小时 30 分钟理解做法,45 分钟敲代码。
我是不是好菜啊啊啊。。。
题意
题意不方便表述,还是直接去看原题的题目描述吧。。。
解法
wqs 二分 + 二分队列。
一
考虑暴力的 DP 转移,
答案则是
其中
记
感性理解发现题目具有决策单调性,即
只利用
同时利用两个条件,调整 DP 顺序(即四边形不等式优化),可以做到
但是优化还不够。
设
考虑构造一个新函数
考虑构造如下的新问题:
这个问题是可以
那只要让
注意到
故满足单调性,考虑二分
以上是 wqs 二分的证明及过程,下面讲讲构造的新问题的解法。
二
考虑前
其中
记
考虑维护一个集合
不妨把这个区间记做
对于已经算出来的
上面那句话(指的是从 “ 不妨 ” 开始到这里)没看懂怎么办?没关系,往下看。(看懂的大佬可以直接跳到代码。)
具体化地,根据决策单调性的性质,有
考虑使用一个队列维护
转移到
考虑更新队列,如果整个最优决策区间都比
画个图:
现在删去了队尾完全无用的转移点,考虑有一个转移区间有些有用有些没用,即
显然这样的
画个图:
别说我字丑,电脑写字不方便,不知道为什么不能输入字只能写。
复杂度:wqs 二分 + 二分队列,一共是
补充
一、关于 g_i+ti 也是凸的的证明
由于 (这个显而易见吧,不懂的看其他题解),那么有
故
二、关于 w 的 O(1) 计算
显然在这段区间的中位数处放邮局使得总距离最短。
令
则左半部分对
其中,
求和部分用前缀和预处理一下,右半部分同理可得。
所以
其中,
三、wqs 二分整数而不是实数的证明
其实可以二分实数,不过有可能 TLE。
我们知道,答案
即
我们 wqs 二分的就是斜率,因此本题可以二分整数。对于答案是实数的,可能要使用实数域的二分,同时要控制二分次数,不要 TLE。
AC 代码
#include<bits/stdc++.h>
#define Code using
#define by namespace
#define wjb std
Code by wjb;
#define int long long // 十年 oi 一场空,不开 long long 一场空!
int in()
{
int k=0,f=1;
char c=getchar();
while(c<'0'||c>'9')
{
if(c=='-')f=-1;
c=getchar();
}
while(c>='0'&&c<='9')k=k*10+c-'0',c=getchar();
return k*f;
}
void out(int x)
{
if(x<0)putchar('-'),x=-x;
if(x<10)putchar(x+'0');
else out(x/10),putchar(x%10+'0');
}
const int N=5e5+10;
int n,m;
int a[N];
int sum[N]; // 前缀和数组
struct node // 用于队列
{
int k,l,r;
};
deque<node>q; // 要开双向队列
int w(int l,int r) // O(1) 计算 w
{
int md=(l+r)>>1;
int d1=(md-l)*a[md],d2=(r-md)*a[md];
int s1=sum[md-1]-sum[l-1],s2=sum[r]-sum[md];
return d1-s1+s2-d2;
}
int f[N],h[N]; // f 是最优距离加代价,h 是放的邮局的个数
bool check(int i, int j, int k) // 比较是否更优的函数
{
return f[i]+w(i+1,k)<=f[j]+w(j+1,k);
}
int ff(int t)
{
while(!q.empty())q.pop_front();
q.push_back((node){0,1,n}),f[0]=0;
for(int i=1;i<=n;i++)
{
while(!q.empty()&&q.front().r<i)q.pop_front(); // 找到 i 的最优决策
q.front().l=i;
int j=q.front().k;
f[i]=f[j]+w(j+1,i)+t,h[i]=h[j]+1;
while(!q.empty()&&check(i,q.back().k,q.back().l))q.pop_back(); // 删掉完全无用的区间
int l=q.back().l,r=n,ans=-1;
while(l<=r) // 二分查找 pos
{
int mid=(l+r)>>1;
if(check(q.back().k,i,mid))ans=mid,l=mid+1;
else r=mid-1;
}
if(ans!=-1)q.back().r=ans,q.push_back((node){i,ans+1,n}); // 剖开区间插入 i
}
return h[n];
}
int two(int l,int r) // wqs 二分
{
while(l<r)
{
int mid=(l+r)>>1;
if(ff(mid)>=m)l=mid+1;
else r=mid;
}
ff(l);
return f[n]-l*m;
}
signed main()
{
n=in(),m=in();
for(int i=1;i<=n;i++)a[i]=in(),sum[i]=sum[i-1]+a[i];
out(two(0,1e12)); // 这个值域我也搞不清楚到底要开多大
return 0;
}
后记
-
十年 oi 一场空,不开 long long 一场空!
-
如果有讲述不清楚的,欢迎提问;如果有讲述错误的,欢迎提出修正。
-
能不能顺手点个赞评论一下,关注一下 QWQ