洛谷P8889 [入门赛 #7] 狠狠地切割 (Hard Version)

一、 题目:

题目背景

本题与 H1 的题意完全一致,区别仅在数据范围。在语言月赛中不存在 H2 题目,本题仅用于增加公开赛的区分度,并不严格遵循比赛考察范围,请酌情完成。

题目描述

现给你一个长度为 n 的序列 a1​,⋯,an​ 和 m 个互不相同的整数 b1​,⋯,bm​。你需要按照这 m 个数对序列 a 进行狠狠地切割

具体的,对于一个数字 i∈[1,n],如果存在一个整数 j∈[1,m],使得 ai​=bj​,则将位置 i 称为一个切割点。对序列 a 中的每一个切割点,我们在这个位置进行一次狠狠地切割。方法是,将该位置的数字去除,然后在这个位置将其左右的序列/片段一分两半。

如果对狠狠地切割的定义有疑问,可以参照「样例 #1」及「样例解释 #1」进行理解。

你需要计算,在进行了所有可能的狠狠地切割后,序列被切割为了多少片段

特别的,如果在切割后,某一段内没有数组,那这一段不可被叫做片段。同样的,如果 1 或 n 为切割点,其与开头和结尾之间也不存在片段。

如果对片段的概念有疑问,可以参照「样例 #2」及「样例解释 #2」进行理解。

输入格式

第一行为两个整数,依次表示序列 a 的长度 n 和序列 b 的长度 m。
第二行有 n 个整数,第 i 个整数表示 ai​。
第三行有 m 个整数,第 i 个整数表示 bi​。

输出格式

输出一个整数,代表狠狠地切割后的片段的个数。

输入输出样例

输入 #1复制

6 2
3 4 3 5 2 6
5 4

输出 #1复制

3

输入 #2复制

6 3
3 4 3 5 2 6
3 5 6

输出 #2复制

2

样例解释

980b0da63b0d4c72892fc0887cb789f8.png



P8889 [入门赛 #7] 狠狠地切割 (Hard Version) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

数据范围

a0de9bac544c4f0ab1c4858b9a6d8cfa.png

二、解析

首先我们先明白暴力的做法:将两个序列分别放入两个数组a、b。两个for循环遍历找出a数组与b数组中相同的数进行处理。很明显这个步骤的时间复杂度是O(n*m),根据题目里n、m范围可知肯定是超时了。我们肯定要对这里进行优化。

 

因为两个序列a,b均是无序的,我们先将两个个序列存入数组,然后用sort函数分别对两个数组进行升序排序。又因为题目规定的片段要与原有序列的顺序相同,所以对于序列a,我们选择用一个存放pair<int,int>的容器来存放序列,其中第一个元素为序列的值,第二个元素为序列索引(从1开始)。这样我们可以对第一个元素按大小排序后,再按索引把序列变回原来的顺序。

 

而后就是寻找相同的值的过程。这个部分我们可以用双指针的方法。指针i指向a的起点,指针j指向j的起点。当a[i].first==b[j],此时i++,并且我们令a[i].first=0(此处j不自增是因为序列可能会有一样的值,所以还要对序列a检查有没有与b[j]相同的值)。如果a[i].x<b[j],则i++。如果a[i].x>b[j],则j++。这样下来,我们只用了n log2 n的时间复杂度就搞定了标记相同元素的步骤(主要是快排)。

 

而后就是按照序列a第二个元素的值把序列变回原来的顺序。再计算片段的数量。

 

三、代码

(代码有些潦草,见谅)

#include<bits/stdc++.h>
#define x first
#define y second
#define int long long
using namespace std;
typedef pair<int,int> pii;
vector<pii> a;
const int N=5e5+10;
int b[N];
int n,m; 
bool cmp1(pii a,pii b){
	return a.x<b.x;
}
bool cmp2(pii a,pii b){
	return a.y<b.y;
}
signed main(){
	cin>>n>>m;
	for(int i=0;i<n;i++){
		int x1;
		cin>>x1;
		a.push_back({x1,i});
	}
	for(int i=0;i<m;i++){
		cin>>b[i];
	}
	sort(b,b+m);
	sort(a.begin(),a.end(),cmp1);
	int i=0,j=0;
	while(i<n&&j<m){
		if(a[i].x==b[j]){
			a[i].x=0;
			i++;
		}
		else if(a[i].x<b[j]){
			i++;
		}
		else j++;
	}
	sort(a.begin(),a.end(),cmp2);
//		for(int i=0;i<n;i++){
//		cout<<a[i].x<<" ";
//	}
	int ans=0;
	for(int i=0,j=0;i<n;i++){
		if(a[i].x!=0){
			ans++;
			j=i;
			while(a[j].x){
				j++;
			}
			i=j-1;
		}
	}
	cout<<ans;
}

 

四、总结

第一次写题解,有很多不足之处,希望能指出。

 

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值