牛半仙的妹子序列

牛半仙的妹子序列

牛半仙有 n 个妹子,魅力值分别为 1 ~ n,排成一排。

牛半仙会在这些妹子中选若干个,但是他很 贪婪,他只会选完美妹子序列。

一个妹子序列 { p 1 , p 2 , p 3 … p m p_1, p_2, p_3 … p_m p1,p2,p3pm} ( 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 为结尾的方案数 那么可以贡献到 ij 满足条件

  1. j < i j<i j<i
  2. v j < v i v_j<v_i vj<vi
  3. 区间 ( 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

如果 ji 有贡献

那么 ji 对应的我们上面说的那个区间中 且区间中 j 的后面没有价值比它大的点

所以对 i 有贡献的点形成的就是一个下降的序列 且属于这个区间

所以可以在每个前部分的点上 维护一个序列 满足序列中从这个点往前走价值都是上升的 (显然我们不需要存具体哪些点 只需要存贡献和)

考虑还有后部分的点的限制呢 如果在后部 i 的前面有一个价值比它小的点 x
那么对 i 有贡献的点的 pos 必须大于 xpos 于是区间的就不是前缀区间了 但是我们维护的序列都是从第一个数开始的 咋办

其实就是减去前面的贡献就行了 这个区间的下降序列的第一个点 一定是价值最大的那个点

所以用区间最后一个点的序列贡献 减去区间序列中第一个点维护的序列贡献 再加上它自己的贡献 我们要求的了

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;
} 
posted @ 2022-10-10 20:19  缙云山车神  阅读(32)  评论(0编辑  收藏  举报