Educational Codeforces Round 89 (Rated for Div. 2) E. Two Arrays

题意

出一个长度为 n 的序列 a ,再给出一个长度为 m 的序列 b ,题目保证序列 b 是严格递增的,我们需要将 a 分割成恰好 m 段,使得每一段的最小值恰好等于 b[ i ] ,问有多少种分割方法
#思路
如果知道每一段的左右区间的取值范围后,根据乘法原理不难算出答案,但又因为每一段的左端点和右端点都是不断变化的,看似不太好直接求解,仔细分析一下不难看出,因为相邻的两段之间是拼接而成的,所以上一段的终点,也就决定了下一段的起点,反之亦然,又因为序列 b 是严格递增的,且需要求的是序列 a 中的最小值,所以倒着处理比较方便,这样一来我们就可以求出每一个 b[ i ] 对应在序列 a 上的起点的左右区间,然后利用乘法原理计算答案就好了。
举个例子,就拿样例 1 来说:序列 a 为 { 12 10 20 20 25 30 } ,序列 b 为 { 10 20 30 }倒着来看的话,如果想要让 min( a[ l ] : a[ r ] ) = b[ 3 ] 的话,只能在序列 a 中取 [ 6 , 6 ] 这段区间,这样一来,b[ 2 ] 终点的位置就确定为 5 了,看一下 b[ 2 ] 的起点,可以选择 3 也可以选择 4 ,即在序列 a 中选择区间 [ 3 , 5 ] 和 [ 4 , 5 ] 都可以满足 min( al : ar ) = b[ 2 ] ,此时因为 b[ 1 ] 的起点一定是位置 1 ,而 b[ 1 ] 的终点已经由 b[ 2 ] 的起点决定了,所以这个样例的答案为 2 。
到这里可能会有一个疑问,假如 b[ k + 1 ] 起点的选择范围是 [ x , y ] ,b[ k ] 当前选择的起点为 z ,正常来说当 b[ k + 1 ] 这段选择的起点为 x 时,b[ k ] 这段选择的区间是 [ z : x - 1 ] ,这里的 min( a[ z ] : a[ x - 1 ] ) = b[ k ] 是显然的,那么当 b[ k + 1 ] 这段如果选择的起点是 x + 1 时,如何保证 min( a[ z ] : a[ x ] ) 这段也是 b[ k ] 呢?因为之前 a[ x ] 这个元素包含在 b[ k + 1 ] 这段中,所以 a[ x ] >= b[ k + 1 ] ,又因为 b[ k + 1 ] > b[ k ] (已知条件),所以 a[ x ] >= b[ k + 1 ] > b[ k ] ,所以 min( a[ z ] : a[ x ] ) = min( min( a[ z ] : a[ x - 1 ] ) , a[ z ] ) = min( b[ k ] , a[ x ] ) = b[ k ]说道这里就可以想到维护一个最小值的后缀然后判断了,设 mmin[ i ] = min( a[ i ] : a[ n ] ) ,根据上面的那一段可知,如果我们处理到了第 k 个位置,也就是需要处理第 b[ k ] 段的区间,只要 [ k + 1 , m ] 这些区间都合法的话,那么 mmin[ i ] = b[ k ] 的这些位置都是可以在第 k 段上当做起点的位置,且是连续的,因为数据比较大,所以用 map 离散的记一下数

#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<queue>
#include<map>
using namespace std;
typedef long long LL;
const int inf=0x3f3f3f3f;
const int N=2e5+100;
const int mod=998244353;
int a[N],b[N];
int mmin[N];
map<int,int>cnt;
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",a+i);
	for(int i=1;i<=m;i++)
		scanf("%d",b+i);
	mmin[n+1]=inf;
	for(int i=n;i>=1;i--)
	{
		mmin[i]=min(mmin[i+1],a[i]);
		cnt[mmin[i]]++;
	}
	if(mmin[1]!=b[1])
		return 0*puts("0");
	LL ans=1;
	for(int i=2;i<=m;i++)
		ans=ans*cnt[b[i]]%mod;
	printf("%lld\n",ans);
    return 0;
}

自己的解法,没想到维护后缀。记录每个b[i]最后出现的位置,从后往前计数,由于b是严格递增的,不需要担心位置重叠问题。但这个方法特判比较多,不像上面那个方法。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
#include <cmath>
#include <vector>
#include <map>
using namespace std;
#define inf 0x7fffffff
#define ll long long
#define maxn 200005
#define mod (998244353)
ll a[maxn], b[maxn], r[maxn];
int main(){
    ll i, n, j, k, t, m, ans=1;
    scanf("%lld%lld",&n,&m);
    for(i=1;i<=n;i++) scanf("%lld",&a[i]);
    for(i=1;i<=m;i++) scanf("%lld",&b[i]);
    if(m==1){
    	bool f=false;
    	for(i=n;i>=1;i--){
    		if(a[i]<b[1]){
				printf("0\n");
				return 0;
			}
			if(a[i]==b[1]) f=true;
		}
		if(f)
		printf("1\n");
		else printf("0\n");
			return 0;
	}
    ll cur=m;
    for(i=n;i>=1;i--) 
		if(a[i]==b[cur]) r[cur--]=i;
    if(cur){
        printf("0\n");
        return 0;
    }
    for(i=r[1];i>=1;i--)
        if(a[i]<b[1]){
            printf("0\n");
            return 0;
        }
    for(j=n;j>r[m];j--)
        if(a[j]<b[m]){
            printf("0\n");
            return 0;
        }
    for(i=m;i>1;i--){
        for(j=r[i];j>r[i-1];j--) if(a[j]<b[i]) break;
        if(a[j]<a[r[i-1]]){
        	printf("0\n");
            return 0;
		} 
        ans=ans*(r[i]-j)%mod;
    }
    printf("%lld\n",ans);
}

···