[牛客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<ivj<vimaxk=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;
}

谢 谢 \color{red}{谢谢}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值