P11678

· · 题解

Preface

赛后 20min 调出,,,

Description

定义 f(a_{1\sim m},b_{1\sim m-1}) 的值为:

给定 a_{1\sim n}b_{1\sim n-1},对于任意 2\le i\le n 求出 f(a_{1\sim i},b_{1\sim i-1}) 的值。

### Solution 记 $V=\max{a_i}$。 考虑一个暴力是从左往右 dp,记 $f_{i,j}$ 表示 $x_{1\sim i-1}$ 的取值已经确定、前 $i-1$ 个不等式的限制已经满足且 $x_{i-1}=j$ 的 $\sum\limits_{j<i} b_jx_j$ 的最小值。 则 $f(a_{1\sim i},b_{1\sim i-1})$ 的值就是 $\min\limits_{j\ge a_i} f_{i,j}$。 简单粗暴地可以得到转移方程: $$f_{i,j}\gets \left(\min\limits_{k\ge a_{i-1}-j} f_{i-1,k}\right)+j\cdot b_{i-1}$$ 可以做到 $\mathcal{O}(nV^2)$ 或 $\mathcal{O}(nV)$。 考虑优化,然后就一拍脑袋说:**我猜他是凸的**!看看能不能上个 **slope trick**! 当然有点 corner case:$i\le 2$ 时有些 $f_{i,j}$ 是不合法的,也就是更严谨的是: - 我们断言:**当 $i\ge 3$ 时 $f_{i,*}$ 是下凸的**。 套路地考虑 **归纳证明**: - 当 $i=3$ 时,不妨对初始的 corner case 暴力讨论一下: - 当 $i=1$ 时只有 $f_{1,0}=0$,其余值均可以视作 $\infty$。 - 当 $i=2$ 时那就是 $j\ge a_1$ 部分是一条从 $(a_1,a_1b_1)$ 开始斜率为 $b_1$ 的射线。其余部分为 $\infty$。 - 当 $i=3$ 时: - 若 $a_2\le a_1$ 则全局范围内为一条从 $(0,a_1b_1)$ 开始斜率为 $b_2$ 的射线。 - 若 $a_2>a_1$ 则全局范围内为一条 $(0,a_2b_1)-(a_2-a_1,a_1b_1+(a_2-a_1)b_2)$ 斜率为 $b_2-b_1$ 的线段和一条从 $(a_2-a_1,a_1b_1+(a_2-a_1)b_2)$ 开始的斜率为 $b_2$ 的射线。 - 则当 $i=3$ 时 $f$ 已在 **全局范围** 内构成了一个 **下凸壳**。 - 当 $i>3$ 时且我们已经说明 $f_{i-1,*}$ 是下凸的: - 找到 $f_{i-1,*}$ 中 **最小值的(第一个)取值点** $p$,则考察 $f_{i-1,*}$ 的差分序列 $\mathrm{d}f_{i-1,j}=f_{i-1,j}-f_{i-1,j-1}$ 有: - 对于任意 $1\le k\le p$ 有 $\mathrm{d}f_{i-1,k}<0$。 - 对于任意 $k> p$ 有 $\mathrm{d}f_{i-1,k}\ge 0$。 - 若 $p\ge a_{i-1}$,则此时 **全局斜率变为零**,随后 **全局斜率增加 $b_{i-1}$**。 - 则此时退化为一条射线,显然仍然为凸的。 - 若 $p<a_{i-1}$,则此时我们 **截取出凸壳在 $[p,a_{i-1}]$ 的部分**,将其 **翻转**(对应到差分序列上为 **左开右闭区间翻转再取反**),然后 **放置在最前面**,其余部分斜率推平成 $0$,随后 **全局斜率增加 $b_{i-1}$**。 - 全局增加斜率并不影响凸性;考察做此操作之前的凸性:由于对于任意 $k> p$ 有 $\mathrm{d}f_{i-1,k}\ge 0$ 且递增,则 **翻转并取反** 后仍然递增且不大于 $0$,再接上剩余为 $0$ 的部分仍然符合条件。 对于 $i\le 3$ 的我们可以直接 $\mathcal{O}(V)$ 暴力做;对于 $i>3$ 的部分,根据上面的证明发现我们要支持: 1. 区间翻转。 2. 区间位移(即把三个区间 $x,y,z$ 的顺序变为 $y,x,z$)。 3. 区间取反(即把区间中所有值为 $x$ 的数变为 $-x$)。 4. 区间推平为 $0$。 5. 全局加。 使用 **FHQ-Treap** 容易维护。具体实现上: - 注意到 $1,3$ 操作其实操作的区间一致,所以只需要打一个 tag。所以最终需要三个 tag。 - 然后还需要额外 **手动维护 $f_{i,0}$ 的值**。 视实现最终复杂度是 $\mathcal{O}(n\log{V}+V)$ 或者 $\mathcal{O}(V\log{V})$。 可以参考代码,5.4kb,倒也不算长。 ### Code ```cpp #include<bits/stdc++.h> #define int long long // #pragma GCC optimize(3,"Ofast","inline") #define debug(...) fprintf(stderr,__VA_ARGS__) #define ll long long #define bint __int128 #define ull unsigned long long #define uint unsigned int #define ld double #define PII pair<int,int> #define chkmax(a,b) a=max(a,b) #define chkmin(a,b) a=min(a,b) #define rep(k,l,r) for(int k=l;k<=r;++k) #define per(k,r,l) for(int k=r;k>=l;--k) #define cl(f,x) memset(f,x,sizeof(f)) #define pcnt(x) __builtin_popcount(x) #define lg(x) (31-__builtin_clz(x)) using namespace std; void file_IO() { freopen("test.in","r",stdin); freopen("test.out","w",stdout); } bool M1; const int INF=0x3f3f3f3f; const ll INFLL=0x3f3f3f3f3f3f3f3f; const ld eps=1e-9; mt19937 rd(time(0)); struct FHQ_Treap { static const int N=1e6+5; struct node { int lson,rson,siz,val,sum,x,tag1,tag2,tag3; node() { lson=rson=siz=val=sum=tag1=tag2=tag3=0; } node(int a,int b,int c,int d,int e,int f,int g,int h,int i) { lson=a; rson=b; siz=c; val=d; sum=e; x=f; tag1=g; tag2=h; tag3=i; } }; node tree[N]; #define ls(k) tree[k].lson #define rs(k) tree[k].rson void push_up(int k) { tree[k].siz=tree[ls(k)].siz+tree[rs(k)].siz+1; tree[k].sum=tree[ls(k)].sum+tree[rs(k)].sum+tree[k].val; } int p; int new_node(int val) { tree[++p]=node(0,0,1,val,val,(int)rd(),0,0,0); return p; } void upd(int k,int t1,int t2,int t3) { if(!k) return; if(t3) { tree[k].val=tree[k].sum=0; tree[k].tag1=tree[k].tag2=0; tree[k].tag3=1; } if(t1) { swap(ls(k),rs(k)); tree[k].tag1^=1; tree[k].val=-tree[k].val; tree[k].sum=-tree[k].sum; tree[k].tag2=-tree[k].tag2; } tree[k].val+=t2; tree[k].sum+=t2*tree[k].siz; tree[k].tag2+=t2; } void push_down(int k) { int &t1=tree[k].tag1,&t2=tree[k].tag2,&t3=tree[k].tag3; if(t1||t2||t3) { upd(ls(k),t1,t2,t3); upd(rs(k),t1,t2,t3); t1=t2=t3=0; } } int merge(int u,int v) { if(!u||!v) return u|v; if(tree[u].x<tree[v].x) { push_down(u); rs(u)=merge(rs(u),v); push_up(u); return u; } else { push_down(v); ls(v)=merge(u,ls(v)); push_up(v); return v; } } void split(int k,int val,int &u,int &v) { if(!k) { u=v=0; return; } push_down(k); if(tree[k].val<val) u=k,split(rs(k),val,rs(k),v); else v=k,split(ls(k),val,u,ls(k)); push_up(k); } void split_siz(int k,int val,int &u,int &v) { if(!k) { u=v=0; return; } push_down(k); if(tree[ls(k)].siz<val) u=k,split_siz(rs(k),val-tree[ls(u)].siz-1,rs(k),v); else v=k,split_siz(ls(k),val,u,ls(k)); push_up(k); } }; FHQ_Treap T; const int N=5e5+5,M=1e6+5,m=1e6; int a[N],b[N],f[2][M],suf[M]; int query(int &rt,int p) { int u=0,v=0; T.split(rt,0,u,v); int mnpos=T.tree[u].siz; if(mnpos>=p) { int val=T.tree[u].sum; rt=T.merge(u,v); return val; } else { rt=T.merge(u,v); u=v=0; T.split_siz(rt,p,u,v); int val=T.tree[u].sum; rt=T.merge(u,v); return val; } } void solve() { int n; scanf("%lld",&n); rep(i,1,n) scanf("%lld",&a[i]); rep(i,1,n-1) scanf("%lld",&b[i]); cl(f,0x3f); int p=1; f[1][0]=0; rep(i,2,3) { p^=1; rep(j,0,m) f[p][j]=INFLL; suf[m+1]=INFLL; per(j,m,0) suf[j]=min(suf[j+1],f[p^1][j]); rep(k,0,m) { f[p][k]=INFLL; chkmin(f[p][k],suf[max(a[i-1]-k,0ll)]+b[i-1]*k); } int mn=INFLL; rep(j,a[i],m) chkmin(mn,f[p][j]); printf("%lld\n",mn); } int x=f[p][0],rt=0; rep(i,1,m) rt=T.merge(rt,T.new_node(f[p][i]-f[p][i-1])); rep(i,4,n) { int u=0,v=0; T.split(rt,0,u,v); int mnpos=T.tree[u].siz; if(mnpos<a[i-1]) { rt=T.merge(u,v); x+=query(rt,a[i-1]); int w=0; u=v=0; T.split_siz(rt,mnpos,u,v); int t=v; v=0; T.split_siz(t,a[i-1]-mnpos,v,w); T.upd(v,1,0,0); T.upd(u,0,0,1); T.upd(w,0,0,1); rt=T.merge(v,T.merge(u,w)); } else { x+=T.tree[u].sum; rt=T.merge(u,v); T.upd(rt,0,0,1); } T.upd(rt,0,b[i-1],0); printf("%lld\n",x+query(rt,a[i])); } } bool M2; // g++ C.cpp -std=c++14 -Wall -O2 -o C signed main() { // file_IO(); int testcase=1; // scanf("%d",&testcase); while(testcase--) solve(); debug("used time = %dms\n",(signed)(1000*clock()/CLOCKS_PER_SEC)); debug("used memory = %dMB\n",(signed)((&M1-&M2)/1024/1024)); return 0; } ```