9月25日模拟赛题解

前言

估分 \(100+0+100+100\),实际 \(36+0+100+10\)

\(\text{T1}\) 乱搞了个假的 “ \(\log(n)\) ”,\(\text{T2}\) 想到全排列骗分没写(你至少输出impossible也能拿 \(10\) 分呀大聪明),\(\text{T4}\) 没考虑空间直接把所有子串扔到 \(\rm set\) 里。

\(\color{White}{大聪明!}\)

正文

\(\text{T1 ZVRK 函数}\)

\(\text{Description}\)

给定 \(A,B\),求 \(\sum_{i=A}^B \operatorname{lowbit}(i)\)

\(1\le A,B\le10^{15}\)

\(\text{Solution}\)

显然有一个 \(\mathcal{O}(n)\) 算法。

考虑正解,要么是 \(\mathcal{O}(\log n)\),要么是 \(\mathcal{O}(1)\)显然前者可能性较大

看一下各种数对于答案的贡献:

  1. 奇数:每个的贡献为 \(1\)
  2. \(2\) 的倍数但不是 \(4\) 的倍数:每个的贡献为 \(2\)
  3. \(4\) 的倍数但不是 \(8\) 的倍数:每个的贡献为 \(4\)
  4. \(\cdots\cdots\)

那么把每一种的个数求出来然后乘上对应贡献即可,因为答案满足区间可减性,所以只需要求出 \(1\sim B\)\(1\sim (A-1)\) 中的答案,过程用短除法。

时间复杂度 \(\mathcal{O}(\log n)\)

\(\text{Code}\)

#include <iostream>
#include <cstdio>
#define int long long
using namespace std;

int solve(int n)
{
	int base = 1, ans = n;
	while (n)
	{
		n >>= 1;
		ans += n * base;
		base <<= 1;
	}
	return ans;
}

signed main()
{
	int a, b;
	scanf("%lld%lld", &a, &b);
	printf("%lld\n", solve(b) - solve(a - 1));
	return 0;
}

\(\text{T2 奇怪的队列}\)

\(\text{Description}\)

\(n\) 个人排队,每个人的身高都是一个整数 \(a_i\),且互不相同。每个人记住了前面比自己高的人的个数 \(b_i\),也可能记住了后面比自己高的人的个数,而且他们不知道自己记住的是哪一个方向。

请给出一个排队顺序,使得任意一个人的两个方向中至少有一个方向上的比他高的人数和他记住的数字相同。如果有多个答案满足题意,输出字典序最小的。如果不存在满足题意的排列,输出impossible

\(n\le10^5\)

\(\text{Solution}\)

按身高从高到矮排序,从第 \(1\) 个人枚举到第 \(n\) 人。当枚举到第 \(i\) 个人时,前面的 \(i-1\) 个人都比他高,则排在第 \(b_i\) 个人后面和排在第 \(i-1-b_i\) 个人后面是相同的。

那么impossible的情况就是发现 \(b_i>i-1\)

贪心的正确性:当第 \(i\) 个人进来时,前面的 \(i-1\) 个人都比他高,则前面 \(i-1\) 个人的排列顺序对第 \(i\) 个人没有影响。

时间复杂度 \(\mathcal{O}(n\log n)\)

\(\text{Code}\)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;

const int MAXN = 1e5 + 5;

struct node
{
	int a, b;
}p[MAXN];

bool cmp(node x, node y)
{
	return x.a > y.a;
}

vector<int> ans;

int main()
{
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		scanf("%d%d", &p[i].a, &p[i].b);
	}
	sort(p + 1, p + n + 1, cmp);
	for (int i = 1; i <= n; i++)
	{
		if (p[i].b > i - 1)
		{
			puts("impossible");
			return 0;
		}
		int pos = min(p[i].b, i - 1 - p[i].b);
		ans.insert(ans.begin() + pos, p[i].a);
	}
	for (int i = 0; i < n; i++)
	{
		printf("%d ", ans[i]);
	}
	return 0;
}

\(\text{T3 质数}\)

\(\text{Description}\)

求在区间 \(L\sim R\) 内有多少个数是一个质数,或是两个质数的乘积。

\(L,R\le10^7\)

\(\text{Solution}\)

巨水的送分题。

\(\text{T1}\) 相同,答案同样满足区间可减性。

质数直接用线性筛,当 \(i\) 是质数时给 \(i\) 乘上质数即可处理第二种。

时间复杂度 \(\mathcal{O}(10^7+q)\)

\(\text{Code}\)

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;

const int MAXN = 1e7 + 5;
const int n = 1e7;

int p[MAXN], v[MAXN], dp[MAXN];
bool f[MAXN];

void pre()
{
	for (int i = 2; i <= n; i++)
	{
		if (!v[i])
		{
			p[++p[0]] = i;
			v[i] = i;
			f[i] = true; //f[i]表示i是不是满足要求的数
			for (int j = 1; j <= p[0]; j++)
			{
				if (i * p[j] > n)
				{
					break;
				}
				f[i * p[j]] = true;
			}
		}
		for (int j = 1; j <= p[0]; j++)
		{
			if (i * p[j] > n || p[j] > v[i])
			{
				break;
			}
			v[i * p[j]] = p[j];
		}
	}
}

int main()
{
	pre();
	for (int i = 1; i <= n; i++)
	{
		dp[i] = dp[i - 1] + f[i];
	}
	int q;
	scanf("%d", &q);
	while (q--)
	{
		int l, r;
		scanf("%d%d", &l, &r);
		printf("%d\n", dp[r] - dp[l - 1]);
	}
	return 0;
}

\(\text{T4 好文章}\)

\(\text{Description}\)

有一篇文章,由 \(n\) 个小写英文字母组成。给定一个整数 \(m\),求文章中有多少个不相同的长度为 \(m\) 的子串。

对于 \(30\%\) 的数据,\(1\le m\le n\le200\)

对于 \(50\%\) 的数据,\(1\le m\le n\le2000\)

对于另外 \(20\%\) 的数据,\(1\le m\le 50\le n\le 200000\)

对于 \(100\%\) 的数据,\(1\le m\le n\le200000\)

\(\text{Solution}\)

考场上的思路是把子串全扔到 \(\rm set\) 里,当时想到了 \(\text{T4}\) 不可能那么简单:

#include <iostream>
#include <cstdio>
#include <set>
using namespace std;

const int MAXN = 200005;

string s, t;

set<string> a;

int main()
{
	int n, m;
	scanf("%d%d", &n, &m);
	cin >> s;
	for (int i = 0; i < m; i++)
	{
		t += s[i];
	}
	a.insert(t);
	int st = 0;
	for (int i = m; i < n; i++)
	{
		t.erase(t.begin(), t.begin() + 1);
		t += s[i];
		a.insert(t);
	}
	printf("%d\n", a.size());
	return 0;
}

然后想了想时间是 \(\mathcal{O}((n-m)\log (n-m))\) 的,并没有问题。

然鹅没有注意到空间是 \((n-m)m\) 的,最多可以卡到 \(10^{10}\),然后限制是 \(256MB\),所以成功挂成 \(10\) 分(

另一种显然的思路是 \(\rm hash\)。这里给出另一种不用双哈希的方法(from wjy

对于字符串 \(\text{abcab}\),其 \(\rm hash\) 值为 \(x=\text{a}\times base^4+\text{b}\times base^3+\text{c}\times base^2\text{a}\times base+\text{b}\)

如果变成了 \(\text{bcabc}\),那么值就变成了 \((x-\text{a}\times base^4)\times base+\text{c}\)

然后经过几十次测试,我发现了一组非常棒的 \(base\)\(mod\)

const int MOD = 100000000003;
const int BASE = 114514;

哼,哼,啊啊啊

经实测快速幂会挂掉。

时间复杂度为 \(\mathcal{O}(n+m)\)

\(\text{Code}\)

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

const int MAXN = 200005;
const int MOD = 100000000003;
const int BASE = 114514;

char s[MAXN];
int a[MAXN];

signed main()
{
	int n, m;
	scanf("%lld%lld%s", &n, &m, s + 1);
	for (int i = 1; i <= m; i++)
	{
		a[1] = (a[1] * BASE % MOD + s[i]);
	}
	int diff = 1;
	for (int i = 1; i < m; i++)
	{
		diff = diff * BASE % MOD;
	}
	for (int i = 2; i <= n - m + 1; i++)
	{
		a[i] = ((a[i - 1] - diff * s[i - 1] % MOD + MOD) % MOD * BASE % MOD + s[i + m - 1]) % MOD;
	}
	sort(a + 1, a + n - m + 2);
	int ans = 1;
	for (int i = 2; i <= n - m + 1; i++)
	{
		if (a[i] != a[i - 1])
		{
			ans++;
		}
	}
	printf("%lld\n", ans);
	return 0;
}
posted @ 2021-09-25 17:18  mango09  阅读(36)  评论(0编辑  收藏  举报
-->