1 条题解
-
0
自动搬运
来自洛谷,原作者为

Kubic
Go straight ahead 'til we've lost it all.搬运于
2025-08-24 22:39:33,当前版本为作者最后更新于2022-08-12 15:35:42,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
一个不太一样的做法,想的时候一直以为是 ,写的时候发现实际上很容易 。
首先考虑 怎么做。
可以发现两两可以匹配等价于任意相邻两个可以匹配。
于是考虑对于每个 求出 分别表示 左右两边第一个 的位置。把它看成数轴上的区间 。一组方案合法当且仅当选出的所有 所对应的线段两两不交。
此时有一个非常关键的 observation:这些线段相互之间的关系只有包含和相离。
证明只需反证即可,这里不再赘述。
对于一个线段,如果它包含其它线段,那么选择它肯定不优。称一个线段为“好线段”当且仅当它不包含其它线段。显然我们一定可以选择所有的“好线段”,而这就是我们的最优策略。
此时问题就变为了统计“好线段”的个数。
通过分析可以得到,对于一个 , 是一条好线段等价于 。
我们可以对于每个 求出左右两边第一个比它小的位置,进一步可以求出最大的 使得 是一个“好线段”,设这个值为 。我们的问题就转化为了求出区间中有多少个 的 。直接上主席树即可。
吗?
我们会发现可能会有一个 ,满足 中存在 的值,但是这个值在询问的区间外。这种情况下 实际上是可以选的,但是我们会认为它不能选,导致算出的答案偏小。
一种无脑的解决方案是直接上三维数点干掉这个情况,时空复杂度都是 ,不知道能不能过。
可以观察到,左右两个边界处各有至多一个 被我们漏掉了,考虑如何找到左边界处的 ,右边界处的可以类似处理。
分析 的性质:
-
一定是一个 的前缀最小值。
-
一定满足 ,而对于所有 的前缀最小值而言,满足 的 一定是一段前缀。
-
一定是这段前缀中的最后一个。
上面的三条性质的证明都比较容易,这里不再赘述。
有了这些性质,我们可以直接在 的所有前缀最小值构成的序列上二分找到唯一可能合法的 ,再判断其是否合法即可。
前缀最小值序列可以用可持久化数据结构维护,也可以建成一棵树形结构并把二分改为倍增。
至此,我们在 的时间复杂度内解决了这道题。
参考代码:
#include "towers.h" #include <bits/stdc++.h> using namespace std; #define mid ((l+r)/2) #define clz __builtin_clz const int N=100005,M=4000005,up=1e9; int n,a[N],st[N],w[N],w1[N],w2[N],fa1[17][N],fa2[17][N],mx[17][N]; int cntS,rt[N];struct Seg {int vl,ch[2];}sg[M]; int qMx(int l,int r) { if(l>r) return 0;int t=31-clz(r-l+1); return max(mx[t][l],mx[t][r-(1<<t)+1]); } void upd(int p,int &p1,int l,int r,int x) { sg[p1=++cntS]=sg[p];++sg[p1].vl;if(l==r) return; if(x<=mid) upd(sg[p].ch[0],sg[p1].ch[0],l,mid,x); else upd(sg[p].ch[1],sg[p1].ch[1],mid+1,r,x); } int qSm(int p,int l,int r,int qL,int qR) { if(!p || qL>qR) return 0; if(l>=qL && r<=qR) return sg[p].vl;int res=0; if(qL<=mid) res=qSm(sg[p].ch[0],l,mid,qL,qR); if(qR>mid) res+=qSm(sg[p].ch[1],mid+1,r,qL,qR);return res; } void init(int n1,vector<int> a1) { n=n1;for(int i=1;i<=n;++i) a[i]=mx[0][i]=a1[i-1]; for(int i=1;i<=n;++i) { while(st[0] && a[i]<a[st[st[0]]]) --st[0]; fa1[0][i]=st[0]?st[st[0]]:0;st[++st[0]]=i; for(int j=1;j<=16;++j) fa1[j][i]=fa1[j-1][fa1[j-1][i]]; }st[0]=0;for(int i=0;i<=16;++i) fa2[i][n+1]=n+1; for(int i=n;i;--i) { while(st[0] && a[i]<a[st[st[0]]]) --st[0]; fa2[0][i]=st[0]?st[st[0]]:n+1;st[++st[0]]=i; for(int j=1;j<=16;++j) fa2[j][i]=fa2[j-1][fa2[j-1][i]]; } for(int i=1;i<=16;++i) for(int j=1;j+(1<<i)-1<=n;++j) mx[i][j]=max(mx[i-1][j],mx[i-1][j+(1<<i-1)]); for(int i=1;i<=n;++i) { w1[i]=qMx(fa1[0][i]+1,i-1)-a[i]; w2[i]=qMx(i+1,fa2[0][i]-1)-a[i];w[i]=min(w1[i],w2[i]); if(w[i]>0) upd(rt[i-1],rt[i],1,up,w[i]);else rt[i]=rt[i-1]; } } bool chk(int x,int l,int r,int d) { if(x<l || x>r || w[x]>=d) return 0; if(w1[x]<d && fa1[0][x]>=l) return 0; if(w2[x]<d && fa2[0][x]<=r) return 0;return 1; } int max_towers(int l,int r,int d) { int t,t1=++r,t2=++l,res; res=qSm(rt[r],1,up,d,up)-qSm(rt[l-1],1,up,d,up); for(int i=16;i>=0;--i) {t=fa1[i][t1];if(t>=l && qMx(t+1,r)<a[t]+d) t1=t;} for(int i=16;i>=0;--i) {t=fa2[i][t2];if(t<=r && qMx(l,t-1)<a[t]+d) t2=t;} return res+chk(t1,l,r,d)+(t1!=t2 && chk(t2,l,r,d)); } -
- 1
信息
- ID
- 7876
- 时间
- 2000ms
- 内存
- 2048MiB
- 难度
- 7
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者