2019.9.21模拟赛

2019.9.21模拟赛

T1:奇怪的队列

类似蓝书《算法竞赛进阶指南》上树状数组一节中的一道例题。

不难想到先把所有人按身高排序,这样问题会变得容易下手。

身高按升序排序,然后依次为每个人在队列中安排位置。这样,假设我们已经安排到第x个人,他的身高和记住的数字分别是(a_x)(b_x),现在队列中所有空余的位置上的人的身高都大于(a_x),已经安排好的x-1个位置上的人的身高都小于(a_x)

问题转化为:找一个位置,满足该位置没有被占用,且该位置之前或之后恰有(b_x)个空位。一个位置有没有被占用可以用0/1表示,1表示空位(没有被占用),0表示已经被占用。这样就可以用前/后缀和表示,若第x个人的位置(pos_x)之前恰有(b_x)个空位,且(pos_x)本身也是空位,那么该位置前缀和(sum_x=b_x+1),同理可用后缀和表示(pos_x)之后恰有(b_x)个空位的情况。但同时维护前后缀和比较麻烦,于是我们尝试用前缀和表示(pos_x)之后恰有(b_x)个空位的情况:前x-1个人已经安排好位置,现在还有n-x+1个空位,(pos_x)之后有(b_x)个空位,那么该位置的前缀和就是(sum_x=n-x+1-b_x)

01序列前缀和单调不减,于是我们可以二分找到满足要求的(pos_x)位置,分(pos_x)前/后有(b_x)个人两种情况,题目要求字典序最小,我们又是按升序排序,所以当两个位置都可以的时候选择靠前的位置即可。

注意我们每次安排好第x个人的位置,都会改变(pos_x)位置的0/1情况,即要求动态维护01序列的前缀和。我选择了用树状数组维护,每次需要二分(pos_x),对每次二分到的值在树状数组上查询前缀和,总复杂度(O(nlog^2n))

标程是用线段树维护01序列,利用线段树结构可直接在线段树上二分,类似平衡树查第k大元素的写法,这样复杂度为(O(nlogn))

Code:

#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
const int N = 2e5 + 5, inf = 0x3f3f3f3f;
int n, q[N], c[N];
pii p[N];
void add(int p, int v) {
	for(; p <= n; p += p & -p) c[p] += v;
}
int ask(int p) {
	int ans = 0;
	for(; p; p ^= p & -p) ans += c[p];
	return ans;
}
int calc(int v) {
	int l = 1, r = n, ans = n;
	while(l <= r) {
		int mid = (l + r) >> 1, res = ask(mid);
		if(res >= v) ans = min(ans, mid), r = mid - 1;
		else l = mid + 1;
	}
	return ask(ans) == v ? ans : inf;
}
int main() {
	scanf("%d", &n);
	for(int i = 1; i <= n; ++i) {
		scanf("%d%d", &p[i].first, &p[i].second);
		add(i, 1);
	}
	sort(p + 1, p + n + 1);
	for(int i = 1; i <= n; ++i) {
		int pos = min(calc(p[i].second + 1), calc(n - i + 1 - p[i].second));
		if(pos == inf) return puts("impossible"), 0;
		q[pos] = p[i].first;
		add(pos, -1);
	}
	for(int i = 1; i <= n; ++i) printf("%d ", q[i]);
	return 0;
}

T2:质数

首先考虑单组询问的情况

注意到询问上界R=1e7,可以直接用bool数组标记符合要求的数。质数可以直接(O(n))线性筛求出,对于两个质数的乘积,可以在求出的质数数组中两两枚举相乘进行标记。分析复杂度:看似两重循环,实际上内层循环在两数乘积大于R就break,所有枚举到的乘积都是合法的,且每两个质数的乘积只会被枚举一次。所以复杂度不会超过(O(n))

对于多组询问,对bool数组(O(n))求出前缀和数组,每次(O(1))回答即可。

Code:

#include <bits/stdc++.h>
using namespace std;
const int N = 1e7 + 5;
int T, n, tot, l[N], r[N], p[N], sum[N];
bool mark[N], vis[N];
void prime() {
	for(int i = 2; i <= n; ++i) {
		if(!vis[i]) p[++tot] = i;
		for(int j = 1; j <= tot && i * p[j] <= n; ++j) {
			vis[i * p[j]] = true;
			if(i % p[j] == 0) break;
		}
	}
}
int main() {
	scanf("%d", &T);
	for(int i = 1; i <= T; ++i) {
		scanf("%d%d", &l[i], &r[i]);
		n = max(n, r[i]);
	}
	prime();
	for(int i = 1; i <= tot; ++i) {
		mark[p[i]] = true;
		for(int j = i; j <= tot; ++j) {
			if(1ll * p[i] * p[j] > n) break;
			mark[p[i] * p[j]] = true;
		}
	}
	for(int i = 1; i <= n; ++i) sum[i] = sum[i - 1] + mark[i];
	for(int i = 1; i <= T; ++i) printf("%d
", sum[r[i]] - sum[l[i] - 1]);
	return 0;
}

T3:好文章

字符串算法基本忘光了,只好打hash了。

思路很简单,对整个串hash,之后把每m个字符的hash值塞入set中,最后输出set的size,复杂度(O(nlogn))

数据较大,且可能经过构造,自然溢出会被卡。可以用双模数hash,选两个大质数和底数分别hash,最后比较pair类型。

最初写的时候函数传入参数是bool类型区分两个模数,结果传参时写成1和2,相当于单模数hash,WA了一整页...

Code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> pll;
const int N = 8e6 + 5;
const ll mod1 = 1e9 + 7, mod2 = 1e9 + 9, base1 = 131, base2 = 31;
int n, m;
ll hash1[N], hash2[N], fac_mod1[N], fac_mod2[N];
char s[N];
set<pll> U;
ll ask(int l, int r, bool op) {
	if(op == 1) return ((hash1[r] - hash1[l - 1] * fac_mod1[r - l + 1] % mod1) % mod1 + mod1) % mod1;
	else return ((hash2[r] - hash2[l - 1] * fac_mod2[r - l + 1] % mod2) % mod2 + mod2) % mod2;
}
int main() {
	scanf("%d%d%s", &n, &m, s + 1);
	fac_mod1[0] = fac_mod2[0] = 1;
	for(int i = 1; i <= n; ++i) {
		fac_mod1[i] = fac_mod1[i - 1] * base1 % mod1;
		fac_mod2[i] = fac_mod2[i - 1] * base2 % mod2;
	}
	for(int i = 1; i <= n; ++i) {
		hash1[i] = (hash1[i - 1] * base1 % mod1 + s[i] - 'a' + 1) % mod1;
		hash2[i] = (hash2[i - 1] * base2 % mod2 + s[i] - 'a' + 1) % mod2;
	}
	for(int i = 1; i + m - 1 <= n; ++i)
		U.insert(make_pair(ask(i, i + m - 1, 1), ask(i, i + m - 1, 0)));
	cout << U.size();
	return 0;
}
原文地址:https://www.cnblogs.com/yu-xing/p/11579951.html