CF1142B Solution

CF1142B Solution

题目翻译

题解

这道题的数据范围是\(2e5\),所以算法的时间复杂度需要在\(O(nlogn)\)以内,又因为\(q\le 2e5\),所以要么可以在\(O(logn)\)的复杂度内查询,要么是静态算法。经思考前者不太可行,因此这道题是\(O(nlogn)\)以内的静态算法,可以想到倍增与ST表。(思考过程)

若想使子序列为排列\(p\)的一个轮换,只要保证子序列中每一个元素的前驱与\(p\)中该元素的前驱相同即可,而\(a\)数组中这个前驱的最近位置是固定的。因此设数组\(pre[i][j]\)表示\(a\)数组中下标为\(i\)的数前\(2^j\)个(含\(a[i]\))以\(p\)的轮换排列的数的下标,也就是用倍增的方法记录下以\(a[i]\)为右端点的、符合要求子序列的左端点。对于每个询问,我们只需计算\(l\le i\le r\)时左端点的最大值,如果这个最大值大于\(l\),说明存在符合要求且在\([l,r]\)之间的序列。这个区间最大值可以用ST表处理。

AC代码

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10,logn=18;
int pre[N][20],a[N],p[N],idp[N],ida[N]; 
//pre[i][j]:a数组中下标为i的数前2^j个以p排列的数的下标
//idp[i]:p数组中值为i的元素下标
//ida[i]:a数组中坐标小于目前循环且值为i元素的下标 
int st[N][20],lg[N];
//st[i][j]:a数组中从下标为i的数后2^j个(含a[i])中选择右端点,子序列的最近左端点的下标 
int main()
{
	int n,m,q;
	scanf("%d%d%d",&n,&m,&q); 
	for(int i=1;i<=n;i++) {scanf("%d",&p[i]); idp[p[i]]=i;}
	p[0]=p[n];
	for(int i=1;i<=m;i++) scanf("%d",&a[i]);
	//处理pre数组 
	for(int i=1;i<=m;i++) {pre[i][0]=ida[p[idp[a[i]]-1]]; ida[a[i]]=i;}
	for(int j=1;j<=logn;j++)
		for(int i=1;i<=m;i++) pre[i][j]=pre[pre[i][j-1]][j-1]; 
    //处理ST表
	for(int i=2;i<=m;i++) lg[i]=lg[i>>1]+1;
	for(int i=1;i<=m;i++)
	{
		st[i][0]=i;
		for(int j=logn;j>=0;j--)
			if((n-1)&(1<<j)) st[i][0]=pre[st[i][0]][j];	
	}
	for(int j=1;j<=logn;j++)
		for(int i=1;i+(1<<j)-1<=m;i++) st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
    //回答询问
	int l,r;
	while(q--)
	{
		scanf("%d%d",&l,&r);
		int qwq=lg[r-l+1];
		int qaq=max(st[l][qwq],st[r-(1<<qwq)+1][qwq]);
		if(qaq>=l) printf("1");
		else printf("0");
	} 
	return 0;
}
posted @ 2021-01-03 22:32  violet_holmes  阅读(66)  评论(0编辑  收藏  举报