[牛客2020第4场]牛半仙的妹子序列
牛半仙的妹子序列
题解
《关于我因过于这道题卡常而将其称为"贞卡常"的这件事》
场上被卡常了,下来加了几个优化就卡过去了。
这道题应该很容易看出是一个dp,40pts的
O
(
n
2
)
O(n^2)
O(n2)的dp式子应该是很好想的。
我们定义
d
p
i
dp_{i}
dpi为在只关注第
i
i
i个数到第
n
n
n个数之间的序列构成合法序列的方案数。
容易得到方程式
d p i = ∑ j = i + 1 n [ 满 足 条 件 ] d p j dp_{i}=\sum_{j=i+1}^{n}[满足条件]dp_{j} dpi=∑j=i+1n[满足条件]dpj。
而答案就是我们的 d p 0 dp_{0} dp0。
这种方式的dp是明显可以进行优化的,我们先考虑
d
p
i
dp_{i}
dpi对那些值产生了贡献。
容易发现,
d
p
i
dp_{i}
dpi产生贡献的
d
p
j
dp_{j}
dpj满足条件
[
j
<
i
∧
v
j
<
v
i
∧
m
a
x
k
=
j
+
1
i
{
d
p
k
<
d
p
j
}
]
[j<i \wedge v_{j}<v_{i} \wedge max_{k=j+1}^{i}\{dp_{k}<dp_{j}\}]
[j<i∧vj<vi∧maxk=j+1i{dpk<dpj}]。
而这个条件我们需要想办法对其进行维护。
对于这个我们可以先按权值建一棵FHQ_Treap,每次进行操作时将值在
[
v
i
,
n
+
1
)
[v_{i},n+1)
[vi,n+1)中的树给裂出去,在那棵值域小于
v
i
v_{i}
vi的数加上
d
p
i
dp_{i}
dpi。
但我们如何保证后半段条件呢?
我们可以对Treap上的每个点加上一个
v
a
l
i
val_{i}
vali的值,表示
i
i
i号节点的坐标。而每次操作后都将操作的那个
d
p
i
dp_{i}
dpi对应的点从树中删掉。保证剩下的点都的
v
a
l
val
val值都小于删掉的点。加的过程中当且仅当所有的在点
j
j
j右边的点(即在原序列中
v
v
v值比它大的点)的
v
a
l
val
val值都小于它时才对其进行加dp值得操作。
由于这个
l
z
y
lzy
lzy值得加操作比较复杂,可能会被卡到
l
o
g
n
log_{n}
logn,总时间复杂度
O
(
n
l
o
g
n
2
)
O(nlog^2_{n})
O(nlogn2)存在着被卡掉的风险,所以我们需要对其加几个优化。
我们用一个二元组
(
x
i
,
y
i
)
(x_{i},y_{i})
(xi,yi)表示
l
z
y
i
lzy_{i}
lzyi。
其中
x
i
x_{i}
xi表示当前懒标记加值得大小,而
y
i
y_{i}
yi表示需要点的
v
a
l
val
val值大于多少时才能加上当前的
l
z
y
lzy
lzy值。
我们知道,当前点如果可以加上
x
i
x_{i}
xi的话,必定满足
v
a
l
i
val_{i}
vali大于其右边子树的最大值和
y
i
y_{i}
yi
1.如果当前子树的整体都小于
y
i
y_{i}
yi的话就回退。
2.如果当前
y
i
y_{i}
yi等于新加的
l
z
y
lzy
lzy的值的话就不用下传。
其实这两个剪枝再实际操作中都很有作用,可以证明我们最后的时间复杂度是可以过这道题的。
还有一些细枝末节的卡常技巧这里不做阐释。
源码
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
#include<set>
using namespace std;
#define MAXN 200005
typedef long long LL;
const int mo=998244353;
typedef pair<int,int> pii;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
_T f=1;x=0;char s=getchar();
while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
x*=f;
}
int n,a[MAXN],ad[MAXN];
int add(const int x,const int y){return x+y<mo?x+y:x+y-mo;}
int Max(const int x,const int y){return x>y?x:y;}
class FHQ_Treap{
private:
int ch[MAXN][2],rnd[MAXN],val[MAXN];
int sum[MAXN],maxx[MAXN];pii lzy[MAXN];
int siz[MAXN],tot,root;
int newnode(const int v){int x=++tot;siz[x]=1;rnd[x]=rand();maxx[x]=val[x]=v;return x;}
inline void updata(const int x){
siz[x]=siz[ch[x][0]]+siz[ch[x][1]]+1;maxx[x]=val[x];
if(ch[x][0])maxx[x]=Max(maxx[x],maxx[ch[x][0]]);
if(ch[x][1])maxx[x]=Max(maxx[x],maxx[ch[x][1]]);
}
void downdata(const int x){
if(!lzy[x].first)return ;
if(maxx[x]<lzy[x].second){lzy[x]=make_pair(0,0);return ;}
const int tm2=Max(maxx[ch[x][1]],lzy[x].second-1);
if(tm2<val[x])sum[x]=add(sum[x],lzy[x].first);
if(ch[x][1]){
if(lzy[ch[x][1]].second==lzy[x].second)lzy[ch[x][1]].first=add(lzy[ch[x][1]].first,lzy[x].first);
else downdata(ch[x][1]),lzy[ch[x][1]]=lzy[x];
}
const int tm1=Max(val[x],maxx[ch[x][1]]);
if(ch[x][0]&&maxx[ch[x][0]]>tm1){
const int tmp=Max(lzy[x].second,tm1);
if(lzy[ch[x][0]].second==tmp)lzy[ch[x][0]].first=add(lzy[ch[x][0]].first,lzy[x].first);
else downdata(ch[x][0]),lzy[ch[x][0]]=make_pair(lzy[x].first,tmp);
}
lzy[x]=make_pair(0,0);
}
int merge(const int a,const int b){
if(!a||!b)return a+b;
downdata(a);downdata(b);
if(rnd[a]<rnd[b]){
ch[a][1]=merge(ch[a][1],b);
updata(a);return a;
}
ch[b][0]=merge(a,ch[b][0]);
updata(b);return b;
}
void split(const int now,const int k,int &x,int &y){
if(!now){x=y=0;return ;}downdata(now);
if(k<a[val[now]])y=now,split(ch[now][0],k,x,ch[now][0]);
else if(k==a[val[now]])x=now,y=ch[now][1],ch[now][1]=0;
else x=now,split(ch[now][1],k,ch[now][1],y);
updata(now);
}
void build(int &rt,const int l,const int r){
if(l>r)return ;
int mid=l+r>>1;rt=newnode(ad[mid]);
build(ch[rt][0],l,mid-1);
build(ch[rt][1],mid+1,r);
updata(rt);
}
public:
void Build(){maxx[0]=-1;build(root,0,n);lzy[root]=make_pair(1,0);}
inline void query(const int v){
int x1,y1,x2,y2;split(root,v-1,x1,y1);
split(y1,v,x2,y2);downdata(x1);downdata(x2);
lzy[x1]=make_pair(sum[x2],0);root=merge(x1,y2);
}
int ask(){downdata(root);return sum[root];}
}Tree;
signed main(){
srand(191919);
read(n);for(int i=1;i<=n;++i)read(a[i]),ad[a[i]]=i;
Tree.Build();for(int i=n;i;--i)Tree.query(a[i]);
printf("%d\n",Tree.ask());
return 0;
}