昨天被学军的公开赛虐傻了,才发现自己还不会用CDQ优化DP,吓得赶紧去填坑。。。
普通的CDQ就是对二分操作,计算前半部分的插入对后半部分的询问的影响。
那么如何用CDQ优化DP呢?
看一道例题:
NOI2007 cash
不难推出平方的dp方程:
f[i] = max(f[i-1], f[j]/(R[j]*A[j]+B[j])*R[j]*A[i] + f[j]/(R[j]*A[j]+B[j])*B[i]) (j
复杂度是O(n^2)的
由于这些变量中并不存在单调关系,所以使不能用单调性优化的
观察式子,我们设x[j] = f[j]/(R[j]*A[j]+B[j])*R[j],y[j] = f[j]/(R[j]*A[j]+B[j])
不难吧转移方程写成如下形式
f[i] = max(f[i-1], x[j]*A[i] + y[j]*B[i]) (j
点积的形式?不难发现,这样的最大值一定是在点集(x[j],y[j])的凸包上的点,更确切的说,这个点能与点(A[i],B[i])组成凸包的偏上的切线
很明显我们可以用平衡树来维护上凸壳,然后再凸壳上找到切线。
问题似乎的到了解决,但是这样的变成复杂度太高了,几乎很难在赛场上写出并调试正确。
于是我们就有了一个强有力的替代品——CDQ分治。
我们可以看到dp的过程就是不断的计算出新的f值再用其组成新的点来放进凸包中
其实我们可以把这个过程理解成这样:计算f[1],在平面上插入(x[1],y[1]),计算f[2],在平面上插入(x[2],y[2])……
这样我们就可以用CDQ分治来优化这个过程了
具体过程如下:
Solve(l,r)
Solve(l,mid)
利用f[l~mid]更新f[mid+1,r]
Solve(mid+1,r)
这题就是利用l~mid之间的点组成的凸壳来更新mid+1~r的dp值,由于凸包上的边斜率单调,所以可以离线后把询问的斜率排序,O(n)搞出来
由于用到了排序,所以总的复杂度为O(nlog^2n)
代码很短的,而我写得很丑。。。。
#include
#include
#include
using namespace std;
const double Eps = 1e-12;
const int Maxn = 100005;
int n,i,j,cnt1,cnt2,tot;
double f[Maxn], R[Maxn];
double A[Maxn], B[Maxn];
int p[Maxn];
struct Point{
double x,y;
Point operator -(const Point &a)const{
return (Point){x-a.x, y-a.y};
}
bool operator <(const Point &a)const{
return (x>1, k=0;
for (int i=l,j=mid+1;i<=mid||j<=r;k++)
if (j>r || (i<=mid && q[i]f[l]) f[l] = f[l-1];
dot[l].x = f[l]/(R[l]*A[l]+B[l])*R[l];
dot[l].y = f[l]/(R[l]*A[l]+B[l]);
return;
}
int mid = (l+r)>>1;
solve(l,mid);
up[cnt1=1] = dot[l];
for (int i=l+1;i<=mid;i++){
while (cnt1>1 && mult(up[cnt1-1],up[cnt1],dot[i])>=-Eps) cnt1--;
up[++cnt1] = dot[i];
}
dw[cnt2=1] = dot[l];
for (int i=l+1;i<=mid;i++){
while (cnt2>1 && mult(dw[cnt2-1],dw[cnt2],dot[i])<=Eps) cnt2--;
dw[++cnt2] = dot[i];
}
tot = 0;
for (int i=1;i1;i--) con[++tot] = dw[i];
if (tot==0) con[++tot]=dot[l];
if (tot<=2){
for (int i=mid+1;i<=r;i++)
for (int j=1;j<=tot;j++)
if (f[i] Eps ) j=j%tot+1;
if (f[p[i]]
同样可以先把方程列出来:
f[i] = min(f[j]+(a[i]-a[j])^2) (j=b[i])
与上题不同的是这里多了一个a[j]>=b[i]的限制,我们逐步来分析。
如果a[i]单调,这很明显是可以用单调性来优化的。
我们可以利用CDQ强行上单调性,也就是说把l~mid强行按a值排序再更新mid+1~r——就是强行造出单调队列,再在这个队列里二分找到最优解
题目里面给了不考虑b的限制的部分分,利用上面的方法就可以了
加上b的限制也是很简单的:把mid+1~r这部分按b排序,在插入l~mid的时候,适时地查询更新mid+1~r的解,复杂度不变
#include
#include
#include
#include
using namespace std;
const int Maxn = 100005;
typedef long long LL;
LL f[Maxn];
int A[Maxn], B[Maxn];
int q[Maxn], p[Maxn];
int n,i,head,tail;
bool cmpA(const int &a,const int &b){
return A[a] > A[b];
}
bool cmpB(const int &a,const int &b){
return B[a] > B[b];
}
LL F(int j,int k){
return ( f[j]+(LL)A[j]*A[j] )-( f[k]+(LL)A[k]*A[k] );
}
LL G(int j,int k){
return (A[j]-A[k]);
}
LL cc(int j,int i){
return f[j] + (LL)(A[j]-A[i])*(A[j]-A[i]);
}
LL calc(int x){
if (head>tail) return f[n+1];
LL ret = tail;
int L = 1, R = tail-1;
while (L<=R){
int mid = (L+R)>>1;
if (cc(q[mid],x) < cc(q[mid+1],x))
R = mid-1, ret = mid;
else L = mid+1;
}
ret = q[ret];
return cc(ret,x);
}
void solve(int l,int r){
if (l==r) return;
int mid = (l+r)>>1;
solve(l,mid);
int i,j;
for (i=l;i<=r;i++)
p[i] = i;
sort(p+l,p+mid+1,cmpA);
sort(p+mid+1,p+r+1,cmpB);
head = 1; tail = 0;
for (i=l,j=mid+1;i<=mid;i++){
while (j<=r && B[p[j]]>A[p[i]]){
LL tmp = calc(p[j]);
if (tmp=f[n+1]) printf("-1\n");
else
printf("%.4lf\n",(double)sqrt((long double)f[n]) );
return 0;
}