题解 CF1039E 【Summer Oenothera Exhibition】

· · 题解

题意 : (和原题面本质相同)

给定一个长度为 n 的序列。

接下来 q 组询问,每次给出一个常数 k

求最少把序列分为多少段使得每段序列中数的极差不超过 k

允许离线。

------------ 首先不难针对单组询问想到暴力 : 每次贪心划分,能不分就不分。正确性较为显然。 似乎难以得出更强的性质,考虑优化这个过程。 设 $t[i]$ 为第 $i$ 个位置分出一段,最远能到达哪里。 这样,若在 $(i,t[i])$ 之间连边,$dis(1,n+1)$ 就是答案。 这些边会新成一棵树,不难想到使用 $\rm LCT$ 来维护。 接下来,将询问离线,按极差约束从小到大来回答。 显然 $t[i]$ 会逐渐变大,但是总变化次数可能高达 $O(n^2)$,不能承受。 考虑根号分治。 若 $t[i]\leq \sqrt{n}$ 才使用 $\rm LCT$ 维护。 由于 $t$ 只会增加,这部分的总变化次数为 $O(n\sqrt{n})$。 这样会形成森林而非单棵树,可以发现,由于超过 $\sqrt{n}$ 的边才会导致断开,森林中树的总数不超过 $O(\sqrt{n})$。 我们利用 $\rm LCT$ 每次跳过一棵树。 接下来,就是 $O(n\sqrt{n})$ 个单点求解 $t$ 的询问。 这里使用 $\rm ST$ 表配合二分,写成倍增常数会小一些。 总复杂度 $O(n\sqrt{n}\log n)$。 下面是实现细节 : - $t[i]\leq \sqrt{n}$ 的 $O(n\sqrt{n})$ 次变化如何找到。 每次 $t[i]$ 变化后,二分出下一次变化所需的极差,然后用 `vector` 来分配给每个询问。 - 怎么用 $\rm LCT$ 每次跳过一棵树。 $\rm LCT$ 中编号大的点总是为父亲,这样 $\access$ 一下就得到我们想要的路径了。 ```cpp #include<algorithm> #include<vector> #include<cstdio> #define pb push_back #define ll long long #define MaxN 101000 using namespace std; int read(){ int X=0;char ch=0; while(ch<48||ch>57)ch=getchar(); while(ch>=48&&ch<=57)X=X*10+(ch^48),ch=getchar(); return X; } struct LCT{ struct Node{int l,r,f,c;}a[MaxN]; void Init(int n) {for (int i=1;i<=n;i++)a[i].c=1;} inline bool nrt(int u) {return a[a[u].f].l==u||a[a[u].f].r==u;} inline void up(int u) {a[u].c=a[a[u].l].c+a[a[u].r].c+1;} void rot(int u) { int fa=a[u].f,gf=a[fa].f; if (a[gf].l==fa)a[gf].l=u; if (a[gf].r==fa)a[gf].r=u; a[a[fa].f=u].f=gf; if (a[fa].l==u){ a[a[fa].l=a[u].r].f=fa; a[u].r=fa; }else { a[a[fa].r=a[u].l].f=fa; a[u].l=fa; }up(fa);up(u); } void splay(int u){ while(nrt(u)){ int fa=a[u].f,gf=a[fa].f; if (nrt(fa)&&(a[fa].l==u)==(a[gf].l==fa)) rot(fa); rot(u); } } void access(int u){ int sav=u; for (int v=0;u;u=a[v=u].f) {splay(u);a[u].r=v;up(u);} splay(sav); } int findrt(int u){ access(u); while(a[u].l)u=a[u].l; splay(u);return u; } void link(int u,int v) {access(u);a[u].f=v;} void cut(int u,int v){ access(u);splay(v); a[u].f=a[v].r=0;up(v); } }T; const int INF=2100000000; int x[MaxN]; struct ST { int t0[20][MaxN],t1[20][MaxN],lg2[MaxN]; void Init(int n,int *x){ for (int i=2;i<=n;i++)lg2[i]=lg2[i>>1]+1; reverse(lg2+1,lg2+n+1); for (int i=1;i<=n;i++) t0[0][i]=t1[0][i]=x[i]; for (int j=0;(1<<j+1)<=n;j++) for (int i=1;i+(1<<j+1)-1<=n;i++){ t0[j+1][i]=min(t0[j][i],t0[j][i+(1<<j)]); t1[j+1][i]=max(t1[j][i],t1[j][i+(1<<j)]); } } int qry(int u,int lim,int &l,int &r){ l=INF;r=0; for (int k=lg2[u];~k;k--) if (k<=lg2[u]&&max(r,t1[k][u])-min(l,t0[k][u])<=lim){ r=max(r,t1[k][u]); l=min(l,t0[k][u]); u+=(1<<k); } return u; } int qry2(int u,int lim){ int l,r; return qry(u,lim,l,r); } }S; struct Qry{int l,p;}sq[MaxN]; bool cmpQ(const Qry &A,const Qry &B) {return A.l<B.l;} vector<int> tb[MaxN]; int n,BS,q,t[MaxN]; void upd(int u,int lim) { if (t[u])T.cut(u,t[u]); int l,r,sav=S.qry(u,lim,l,r); t[u]=sav; if (t[u]-u<=BS){ T.link(u,t[u]); if (t[u]<=n){ int nxt=max(r,x[t[u]])-min(l,x[t[u]]), tim=lower_bound(sq+1,sq+q+1,(Qry){nxt,0},cmpQ)-sq; if (tim<=q)tb[tim].pb(u); } } } int solve(int i,vector<int> &b) { int lim=sq[i].l; for (int i=0;i<b.size();i++) upd(b[i],lim); b.clear(); int ret=0; for (int p=1;p<=n;){ int rt=T.findrt(p); ret+=T.a[rt].c-1; if (rt>n)break; p=S.qry2(rt,lim);ret++; }return ret; } int w,ans[MaxN]; int main() { n=read();w=read();q=read(); while(BS*BS<n)BS++; for (int i=1;i<=n;i++)x[i]=read(); x[n+1]=INF;S.Init(n+1,x); for (int i=1;i<=q;i++){ sq[i].l=w-read(); sq[i].p=i; }sort(sq+1,sq+q+1,cmpQ); T.Init(n+1); for (int i=1;i<=n;i++)tb[1].pb(i); for (int i=1;i<=q;i++) ans[sq[i].p]=solve(i,tb[i]); for (int i=1;i<=q;i++) printf("%d\n",ans[i]-1); return 0; } ``` 听说还有 $O(n^{5/3}+n^{4/3}\log n)$ 的做法? Orz