考虑一个简单的dp,设f[i]表示现在1..i的连线全部被删除,最后一个选的是i的代价。转移有些特别,从j=[1,i)中选出一条不与i-p[i]相交的并且(i,j)中没有不与这两条相交的,就可以f[i]=f[j]+c[i].
想一想答案是什么?
使得(i,n]中所有连线都与i-p[i]相交,则此f[i]可以作为答案。
也就是(i,n]中所有p[j]小于p[i]的
这个dp是O(n^2)的,我们现在来改进一下他。
将一条线i=p[i]考虑成一个二维平面上的点(p[i],i) (为什么要反过来等下会讲到)
观察怎样的j=[1,i)中的点能转移过来。
所有满足p[j]< p[i]且j点与i点所形成的矩形中没有其他点(其他点都与这两条相交)的都可以
那么实际上就是一个x坐标在[1,p[i])区间内的递减单调栈(以y坐标为关键字)内元素(若后面有比他高的都不在单调栈内)的最小f[i]。 (当然我们可能会先插入(0,0)点以便维护)
如果坐标不反过来则不能化简为单调栈的模型。
现在要维护这个东西(区间单调栈内信息,支持插入点),先考虑他有什么修改操作。 注意到每次只会插入一个点.即单点修改.那么我们有如下套路:
考虑二分查询。
设查询函数query(l,r,x)表示l,r区间的单调栈再在后面插入x的最小f[i]值。
为什么要加一个x,是因为后面入栈的元素对这个区间的影响可以归结于一个元素(就是max)
显而易见地,设右区间最大值为rmx,若x>rmx则右边所有元素都被弹走,只需要查一个分支query(l,mid,x),
若x< rmx则要查两个分支query(l,mid,rmx)与query(mid+1,r,x),最后答案取min.
但这样复杂度没有保证,最坏甚至会达到2n log (log是查询rmx带的).
注意到此题修改的特殊性: 仅插入一个点。那么也就是说假如我们将query放到线段树上,即拆成若干个完整的线段树区间query(l,r,x)进行查询,维护每个节点的fmin=query(l,mid,rmx)。然后查询一个完整的线段树区间的复杂度就降低到了log(仅需要走一个分支,而且rmx直接维护即可),总的查询复杂度就是log^2
再考虑修改的问题,只需要修改log个节点,每个节点fmin的我可以直接执行一遍完整线段树的查询,只需要log的时间,所以总的复杂度也只是log^2
感谢来自crazy_czy的详细题解
#include
#include
#include
#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)<(b)?(b):(a))
#define querymax(l,r) (l<=r?(qmret=-1,queryMax(1,1,n,l,r),qmret):0)
using namespace std;
const int N=2e5+10;
int n;
int p[N],c[N],f[N];
int fmin[4*N],gtx[4*N],none=-2139062144;
int qmret;
void queryMax(int x,int l,int r,int tl,int tr) {
if (l>tr || rreturn;
if (tl<=l && r<=tr) {
qmret=max(qmret,gtx[x]);
return;
}
queryMax(x*2,l,l+r>>1,tl,tr);
queryMax(x*2+1,(l+r>>1)+1,r,tl,tr);
}
int _query(int x,int l,int r,int su) {
if (su>gtx[x]) return 1e9;
if (l==r) return f[gtx[x]];
int rmx=gtx[x*2+1];
if (su>rmx) return _query(x*2,l,l+r>>1,su);
int fm1=_query(x*2+1,(l+r>>1)+1,r,su);
return min(fmin[x],fm1);
}
int cnt=0;
int query(int x,int l,int r,int tl,int tr) {
if (l>tr || rreturn 1e9;
if (tl<=l && r<=tr) {
++cnt;
return _query(x,l,r,querymax(r+1,tr));
}
int mid=l+r>>1;
int fm1=query(x*2,l,mid,tl,tr),fm2=query(x*2+1,mid+1,r,tl,tr);
return min( fm1,fm2 );
}
void update(int x,int l,int r) {
gtx[x]=max(gtx[x*2],gtx[x*2+1]);
if (gtx[x*2+1]==none)
fmin[x]=_query(x*2,l,l+r>>1,0); else
fmin[x]=_query(x*2,l,l+r>>1,gtx[x*2+1]);
}
void change(int x,int l,int r,int tg,int v) {
if (l==r) {
gtx[x]=v;
return;
}
int mid=l+r>>1;
if (tg<=mid) change(x*2,l,mid,tg,v); else change(x*2+1,mid+1,r,tg,v);
update(x,l,r);
}
void read(int &x) {
char c; while ((c=getchar())<'0' || c>'9') ;
x=c-'0'; while ((c=getchar())>='0' && c<='9') x=x*10+c-'0';
}
int main() {
freopen("knows.in","r",stdin);
freopen("knows.out","w",stdout);
cin>>n;
for (int i=1; i<=n; i++) read(p[i]);
for (int i=1; i<=n; i++) read(c[i]);
memset(gtx,128,sizeof gtx);
change(1,0,n,0,0);
for (int i=1; i<=n; i++) {
f[i]=query(1,0,n,0,p[i])+c[i];
change(1,0,n,p[i],i);
}
cout<1,0,n,0,n)<