专栏文章

题解:P13788 「CZOI-R6」Permutation and Subsequence

P13788题解参与者 4已保存评论 4

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
4 条
当前快照
1 份
快照标识符
@mio7cq4q
此快照首次捕获于
2025/12/02 14:33
3 个月前
此快照最后确认于
2025/12/02 14:33
3 个月前
查看原文

P13788 题解:

主要思路:

  • 用数组 cc 存储 bb 排列中的每个数在 aa 排列中的位置。
为什么是 aabb 中的位置,而不是 bbaa 中的位置?
阅读题面可以发现,我们要求的是 aa 的连续子段是否是 bb 的子序列,不难发现存储 bbaa 中的位置,再判断位置是否连续会比较好做。
  • 我们设 dpbidp_{b_i} 为以 bib_i 结尾的在 aa 中出现过的子序列的数量,于是不难写出这样的状态转移方程:
dpbi=dpbj+1dp_{b_i}=dp_{b_j}+1
其中 j<ij<icbi1=cbjc_{b_i}-1=c_{b_j}
于是就有了这样的 O(n2)O(n^2) 暴力代码:
CPP
#include <bits/stdc++.h>
#define int long long
using namespace std;
constexpr int N=1e6+7;
int n;
int a[N],b[N],c[N],dp[N];
int32_t main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		c[a[i]]=i;
	}
	for(int i=1;i<=n;i++){
		cin>>b[i];
	}
	int sum=n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<i;j++){
			if(c[b[i]]-1==c[b[j]]){
				dp[b[i]]=dp[b[j]]+1;
				sum+=dp[b[i]];
			}
		}
	}
	cout<<sum;
	return 0;
}
交上去,获得 4040 分。
那么问题又来了,怎么消掉内部循环?
我们不难发现,内部循环遍历时,只有一个 cbjc_{b_j} 满足要求,其余的则都为不必要的操作,那我们就可以用 acbi1a_{c_{b_i}-1} 来代替上文的 cbjc_{b_j}
为什么是 acbi1a_{c_{b_i}-1}
我们都知道,cc 数组存储的是 bb 中的每个值在 aa 中的位置,于是 cbi1c_{b_i}-1 的含义就是 bib_iaa 中的上一个值的位置,于是这一坨的意思也就显而易见了。
知道了这些,我们就可以写出状态转移方程了!
dpbi=dpacbi1+1dp_{b_i}=dp_{a_{c_{b_i}-1}}+1

代码实现:

定义一个变量存储最终答案,再用上文的状态转移方程求和就可以了。
~其实上文那一大坨放代码里也挺清晰的。~
AC Code:
CPP
#include <bits/stdc++.h>
#define int long long
using namespace std;
constexpr int N=1e6+7;
int n;
int a[N],b[N],c[N],dp[N];
int32_t main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		c[a[i]]=i;//存储位置
	}
	for(int i=1;i<=n;i++){
		cin>>b[i];
	}
	int sum=0;//存最终答案
	for(int i=1;i<=n;i++){
		dp[b[i]]=dp[a[c[b[i]]-1]]+1;//状态转移方程
		sum+=dp[b[i]];
	}
	cout<<sum;
	return 0;//完结撒花!!!
}
感谢阅读。
写题解不易,留个赞再走吧!!!

评论

4 条评论,欢迎与作者交流。

正在加载评论...