P10995 【MX-J3-T2】Substring 解题报告

P10995 【MX-J3-T2】Substring

也是在赛时做出黄题了(后话:现在降 橙 \color{#f39c11}\texttt{橙} 力,后话的后话:现在又升 黄 \color{#ffc627}\texttt{黄} 了)。

首先考虑暴力求出所有字串,排序一遍,时间复杂度 O ( n 2 ) O(n^2) O(n2) ,预期得分 45 p t s 45pts 45pts

思考如何不求出所有字串就可以查询出第 k k k 个字串。以样例为例。

3 6
3 1 2
1
2
3
4
5
6

答案:

2 2
2 3
3 3
1 1
1 2
1 3

观察到数字 1 1 1 开头的字串一定大于其他数字开头的字串(证明:根据字典序定义显然),而根据序列性质: 其中 1∼n 各出现了一次 ,数字 1 1 1 开头的子串越长排名越大。具体地 [ 1 ] < [ 1 , 2 ] [1] < [1,2] [1]<[1,2] ,于是根据数字在原数组中的位置,可以求出以数字 x x x 开头的子串数量,并且它们的排名也因此确定了

解释:令 S x S_x Sx 代表以 x x x 开头的子串的集合,那么显然有 S 1 < S 2 < S 3 < . . . < S n S_1 < S_2 <S_3<...<S_n S1<S2<S3<...<Sn (这里比较每个集合内子串字典序大小)。
并且每个 S x S_x Sx 内的排名也是确定且与子串大小相关(如以 3 3 3 开头,长度为 2 2 2 的子串的排名就是以 3 3 3 开头长度为 1 1 1 的字串的排名 + 1 +1 +1,注意这里以 x x x 开头长度为 1 1 1 的字串排名是可以预处理出来的)。

接下来看具体实现:

p o s i pos_i posi 表示第数字 i i i 在原数组出现位置。

l e n i len_i leni 表示以数字 i i i 开头的子串长度。

p r e pre pre l e n len len 的前缀和, p r e i pre_i prei 也就表示以 i i i 开头,长度为 1 1 1 的子串的排名。

于是对于每个查询 k k k ,二分求出其子串开头是什么,然后整个区间根据已知信息,手推一遍样例也就出来了。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#include <map>
#define int long long
using namespace std;

const int maxn = 3e5 + 5;
int n, q, k;
int a[maxn];
map<int, int> pos;
int len[maxn], pre[maxn];

signed main()
{
#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
#endif
	cin >> n >> q;
	for (int i = 1; i <= n; i++)
	{
		scanf("%lld", &a[i]);
		pos[a[i]] = i;
	}
	for (int i = 1, num; i <= n; i++)
	{
		num = pos[i];
		len[i] = n - num + 1;
	}
//	for (int i = 1; i <= n; i++) {
//		cout << len[i];
//	}
//	cout << endl;
	for (int i = 2; i <= n + 1; i++)
	{
		pre[i] = pre[i - 1] + len[i - 1];
	}
//	for (int i = 1; i <= n + 1; i++) {
//		cout << pre[i] << " ";
//	}
//	cout << endl;
	while (q--)
	{
		scanf("%lld", &k);
//		cout << k << " ";
		int x = lower_bound(pre + 1, pre + n + 2, k) - pre - 1;
//		cout << x << "# ";
		if (x == n + 1)
		{
			cout << pos[n] << " " << pos[n] + len[n] - 1 << endl;
		}
		else
		{

			if (k == pre[x])
			{
				cout << pos[x] << " " << pos[x] + k - pre[x] << endl;
			}
			else
			{
				cout << pos[x] << " " << pos[x] + k - pre[x] - 1 << endl;
			}
		}
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值