牛半仙的妹子序列
牛半仙的妹子序列
牛半仙有 n 个妹子,魅力值分别为 1 ~ n,排成一排。
牛半仙会在这些妹子中选若干个,但是他很 贪婪,他只会选完美妹子序列。
一个妹子序列 { p 1 , p 2 , p 3 … p m p_1, p_2, p_3 … p_m p1,p2,p3…pm} ( p i p_i pi指妹子的位置)是完美的,当且仅当其是一 个上升序列,且不存在一个 j,使得 j > p m j > p_m j>pm且 v j > v p m v_j > v_{p_m} vj>vpm,且不存在一个 j 使得 j < p 1 j< p_1 j<p1且 v j < v p 1 v_j < v_{p_1} vj<vp1,且不存在一个 j 使得 p i < j < p i + 1 p_i < j < p_{i+1} pi<j<pi+1, v p i < v j < v p i + 1 v_{p_i} < v_j < v_{p_{i+1}} vpi<vj<vpi+1 。
牛半仙想知道他有多少种选出完美妹子序列的方式。
因为牛半仙忙着和妹子玩耍,所以他想你帮他解决一下问题。
方法数可能很多,牛半仙只想知道对 998244353 取模的结果。
官网给的题解看不懂 看到有大佬说 cdq 分治可以做 然后又看不懂大佬的 cdq 于是就自己歪歪了一个常数巨大的 O ( N log 2 N ) O(N\log^2N) O(Nlog2N) 的 cdq
首先可以想到一个 dp 的做法
f i f_{i} fi 表示以 i 为结尾的方案数 那么可以贡献到 i 的 j 满足条件
- j < i j<i j<i
- v j < v i v_j<v_i vj<vi
- 区间 ( j , i ) (j,i) (j,i) 不存在价值在他们之间的数
O ( N 2 ) O(N^2) O(N2) 可以 dp 直接求
考虑 cdq 分治
我们把价值拿来分治(这里最关键 用下标分治就不行
cdq 分治的过程是求前半部分对后半部分的贡献 于是我们这里求的就是 价值在 [ l , m i d ] [l,mid] [l,mid] 的妹子 对价值在 [ m i d + 1 , r ] [mid+1,r] [mid+1,r] 的妹子的贡献
那么在分治过程中 前半部分的点就满足了条件 2
在 merge 求对后半部分的贡献时候 我们对前后两部分的妹子分别按初始顺序排序
这是为了满足条件 1
如果只考虑条件 1 2 这时对后部分的点 i 有贡献的点 就成了前部分的一个前缀区间
因为对前部分的点要求多了一个 p o s j < p o s i pos_j<pos_i posj<posi 然后前部分的 p o s j pos_j posj 又是上升的 pos 就是初始位置
考虑条件 3
如果 j 对 i 有贡献
那么 j 在 i 对应的我们上面说的那个区间中 且区间中 j 的后面没有价值比它大的点
所以对 i 有贡献的点形成的就是一个下降的序列 且属于这个区间
所以可以在每个前部分的点上 维护一个序列 满足序列中从这个点往前走价值都是上升的 (显然我们不需要存具体哪些点 只需要存贡献和)
考虑还有后部分的点的限制呢 如果在后部 i 的前面有一个价值比它小的点 x
那么对 i 有贡献的点的 pos 必须大于 x 的 pos 于是区间的就不是前缀区间了 但是我们维护的序列都是从第一个数开始的 咋办
其实就是减去前面的贡献就行了 这个区间的下降序列的第一个点 一定是价值最大的那个点
所以用区间最后一个点的序列贡献 减去区间序列中第一个点维护的序列贡献 再加上它自己的贡献 我们要求的了
yzxx大佬给了个新思路 然后下午和机房的人讨论出了个一个 log 的做法 网上也没有 我觉得那是最好理解的一个 相信 yzxx 大佬一定会写博客的
#include<bits/stdc++.h>
#define N 200005
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
#define mid ((l+r)>>1)
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int inf=1e9;
int n,cnt,a[N],lst[N],t[N];
ll anss,sum[N];
bool tag[N];
struct node{ int v,pos; ll ans; }cc[N];
bool cmp_val(node x,node y){ return x.v<y.v; }
bool cmp_pos(node x,node y){ return x.pos<y.pos; }
struct SL_tree{
int maxx[N<<2],pos[N<<2];
void push_up(int rt){
if(maxx[rt<<1]>maxx[rt<<1|1]){ maxx[rt]=maxx[rt<<1]; pos[rt]=pos[rt<<1]; }
else { maxx[rt]=maxx[rt<<1|1]; pos[rt]=pos[rt<<1|1]; }
}
int query(int L,int R,int rt=1,int l=1,int r=cnt){
if(L<=l&&r<=R){ return pos[rt]; }
int x=0,y=0;
if(L<=mid)x=query(L,R,lson);
if(R>mid)y=query(L,R,rson);
if(a[x]>a[y])return x; return y;
}
void build(int rt=1,int l=1,int r=cnt){
if(l==r){ maxx[rt]=a[l]; pos[rt]=l; return; }
build(lson); build(rson); push_up(rt);
}
}tr;
void merge(int l,int r){
ll now=0,x,y; cnt=0;
for(int i=l;i<=mid;i++){
sum[i]=0;
while(now>0&&cc[t[now]].v<cc[i].v)now--;
lst[i]=t[now]; t[++now]=i;
sum[i]=(cc[i].ans+sum[lst[i]])%mod;
a[++cnt]=cc[i].v;
}
tr.build(); now=0;
for(int i=mid+1;i<=r;i++){
while(now>0&&cc[t[now]].v>cc[i].v)now--;
lst[i]=t[now]; t[++now]=i;
}
for(int i=mid+1;i<=r;i++){
x=lower_bound(cc+l,cc+mid+1,cc[lst[i]],cmp_pos)-cc-l+1;
y=lower_bound(cc+l,cc+mid+1,cc[i],cmp_pos)-cc-l; if(x>y)continue;
now=tr.query(x,y);
cc[i].ans=(sum[y+l-1]-sum[now+l-1]+cc[now+l-1].ans+mod+cc[i].ans)%mod;
}
}
void cdq(int l,int r){
if(l==r){ cc[l].ans=max(cc[l].ans,1ll); return; }
cdq(l,mid);
sort(cc+mid+1,cc+r+1,cmp_pos);
merge(l,r);
sort(cc+mid+1,cc+r+1,cmp_val);
cdq(mid+1,r);
sort(cc+l,cc+r+1,cmp_pos);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){ scanf("%d",&cc[i].v); cc[i].pos=i; }
sort(cc+1,cc+1+n,cmp_val);
cdq(1,n);
int now=0;
for(int i=n;i>=1;i--) if(cc[i].v>now)anss=(anss+cc[i].ans)%mod,now=cc[i].v;
cout<<anss;
}