GoodCoder666的个人博客

AtCoder Beginner Contest 250 C~E 题解

2022-05-11 · 11 min read
C++ 算法竞赛

C - Adjacent Swaps

题目大意

NN个球从左到右排成一列。开始时,从左往右的第ii个球上写着数字ii
请执行QQ个操作,第ii个操作如下:

  • j= Nj=~N个球中写着数字xix_i的球的位置
  • 如果j=Nj=N,将其与第j1j-1个球交换;否则,与第j+1j+1个球交换。

求所有操作后的球上分别写的数字。详见输出格式。

2N2×1052\le N\le 2\times 10^5
1Q2×1051\le Q\le 2\times 10^5
1xiN1\le x_i\le N

输入格式

N QN~Q
x1x_1
\vdots
xQx_Q

输出格式

ai=Na_i=N个球中从左往右的第ii在所有操作结束后写的数,则按如下格式输出:
a1 a2  ana_1~a_2~\dots~a_n
a1,,ana_1,\dots,a_n按顺序输出到一行,用空格隔开

样例

略,请自行前往AtCoder查看。

分析

根据数据范围可得,本题只能使用时间复杂度不超过O(N+Qlogn)\mathcal O(N+Q\log n)的算法
因此,暴力模拟,即查找每个球对应的位置jjO(NQ)\mathcal O(NQ))肯定是行不通的。

但是很容易想到可以设置索引数组pp,使当ai=xa_i=x时,px=ip_x=i
这样,对于每一个操作,只需O(1)\mathcal O(1)的时间复杂度就能找到xix_i出现的位置。
交换时注意同时交换一下aapp中的元素即可。总时间复杂度O(N+Q)\mathcal O(N+Q)

代码

#include <cstdio>
#define maxn 200005
using namespace std;

inline void swap(int& x, int& y) { x ^= y ^= x ^= y; }

int pos[maxn], ans[maxn];

int main()
{
	int n, q;
	scanf("%d%d", &n, &q);
	for(int i=1; i<=n; i++)
		ans[i] = pos[i] = i;
	while(q--)
	{
		int x;
		scanf("%d", &x);
		int p1 = pos[x];
		int p2 = p1 == n? p1 - 1: p1 + 1;
		swap(pos[x], pos[ans[p2]]);
		swap(ans[p1], ans[p2]);
	}
	for(int i=1; i<=n; i++)
		printf("%d ", ans[i]);
	return 0;
}

D - 250-like Number

题目大意

当一个正整数kk满足以下条件时,我们称其为“与250250相似的”:

  • k=p×q3k=p\times q^3,其中p,qp,q均为质数,且p<qp<q

求不超过NN的“与250250相似的”kk的个数。

1N10181\le N\le 10^{18}

输入格式

NN

输出格式

将答案输出为一个整数。

样例

NN 输出
250250 22
11 00
123456789012345123456789012345 226863226863

分析

看到数据范围后我们发现NN太大,不能盲目下手。
k=p×q3,kNk=p\times q^3,k\le N可知,p×q3N1018p\times q^3\le N\le 10^{18}
又因为p,qp,q是质数,且p<qp<q可得,2p<q2\le p<q
因此,当pp最小时qq最大,所以qN=1018p=23794000q\le \sqrt[3]{\frac {N=10^{18}} {p=2}}\approx794000

这时,可以想到筛出质数表,并对于每个质数pp计算最大的qq,此时质数p<xqp<x\le q都能作为qq,因此将答案加上p<xqp<x\le q的质数数量即可。当pqp\ge q时,退出循环,输出结果即可。

计算qq时可以使用二分查找或者双指针算法快速处理。
总时间复杂度大约在O(n722)\mathcal O(n^{\frac 7 {22}})

代码

本代码使用双指针实现。

#include <cstdio>
#include <cmath>
#include <vector>
#define maxp 794000
using namespace std;

using LL = long long;

bool bad[maxp];
vector<int> primes;

inline LL pow3(LL x) { return x * x * x; }

int main()
{
	bad[0] = bad[1] = true;
	for(int i=2; i<maxp; i++)
		if(!bad[i])
		{
			primes.push_back(i);
			for(int j=i<<1; j<maxp; j+=i)
				bad[j] = true;
		}
	LL n;
	scanf("%lld", &n);
	LL ans = 0LL;
	for(int i=0, j=primes.size()-1; i<j; i++)
	{
		while(j >= 0 && primes[i] * pow3(primes[j]) > n) j --;
		if(i >= j) break;
		ans += j - i;
	}
	printf("%lld\n", ans);
	return 0;
}

E - Prefix Equality

题目大意

给定长度为NN的正整数序列A=(A1,,AN)A=(A_1,\dots,A_N)B=(B1,,BN)B=(B_1,\dots,B_N)
对于每个1iQ1\le i\le Q,给定两个正整数xi,yix_i,y_i,回答如下格式的查询:

  • 判断集合{A1,,Axi}\{A_1,\dots,A_{x_i}\}{B1,,Byi}\{B_1,\dots,B_{y_i}\}是否相等。

集合可以说成是序列排序并去重的结果,如序列(9,3,5,3,4)(9,3,5,3,4)对应的集合是{3,4,5,9}\{3,4,5,9\}

1N,Q2×1051\le N,Q\le 2\times 10^5
1AiBi1091\le A_i\le B_i\le 10^9
1xi,yiN1\le x_i,y_i\le N

输入格式

NN
A1  ANA_1~\dots~A_N
B1  BNB_1~\dots~B_N
QQ
x1 y1x_1~y_1
\vdots
xQ yQx_Q~y_Q

样例

样例输入

5
1 2 3 4 5
1 2 2 4 3
7
1 1
2 2
2 3
3 3
4 4
4 5
5 5

样例输出

Yes
Yes
Yes
No
No
Yes
No

分析

本题做法很多。这里我们介绍使用哈希(Hash)的算法。
现在我们有一个很简单但明显错误的思路:
AABB做一个前缀和,只计算不重复的元素,即

PA(i)={A1,,Ai}PB(i)={B1,,Bi}P_A(i)=\sum\{A_1,\dots,A_i\}\\ P_B(i)=\sum\{B_1,\dots,B_i\}

此时,只需判断PA(xi)P_A(x_i)PB(yi)P_B(y_i)是否相等即可。时间复杂度为O(N+Q)\mathcal O(N+Q)O(Q+NlogN)\mathcal O(Q+N\log N)
构造hack数据也很简单,只需部分前缀和相等即可,如:

5
1 3 5 6 7
3 2 4 1 5
1
3 3

这样,因为1+3+5=3+2+4=91+3+5=3+2+4=9,所以这样的程序会认为这是相等的序列,从而输出Yes,但显然{1,3,5}{3,2,4}\{1,3,5\}\ne\{3,2,4\},因此答案为No,程序错误。

现在考虑改进这个思路,使其不容易被hack,可以使用一个哈希函数:

H(x)=x(x+A)(x+B)modPH(x)=x(x+A)(x+B)\bmod P

其中A,B,PA,B,P一般取质数,H(x)H(x)即为xx对应的哈希值。(对PP取模是为了防止哈希值太大导致溢出)
显然,这样有一个很小的概率会产生哈希冲突(即不同的数得到相同的哈希值),但因为A,B,PA,B,P的取值太多,评测机没法针对性的hack,所以正常情况下都能通过(CF的Hack机制除外)。如果真担心有问题,可以采取双哈希,即对于一个xx,用两个不同的哈希函数计算哈希值,这样就几乎不可能出现哈希冲突了。

现在,前缀和变为:

PA(i)={H(A1),,H(Ai)}modPPB(i)={H(B1),,H(Bi)}modPP_A(i)=\sum\{H(A_1),\dots,H(A_i)\}\bmod P\\ P_B(i)=\sum\{H(B_1),\dots,H(B_i)\}\bmod P

还是按原来的思路,判断前缀和是否相等即可。
总时间复杂度为O(n)\mathcal O(n)unordered_set/HashSet)或O(nlogn)\mathcal O(n\log n)set/TreeSet)。

代码

这里还是要提一点,就是使用哈希时有一个小技巧,即直接取P=2321P=2^{32}-1unsigned int)或者P=2641P=2^{64}-1unsigned long long),使整数自然溢出,省去了麻烦又耗时间的取模步骤。CodeForces上还是建议取较大的质数(常用的有109+7,99824435310^9+7,998244353)作为PP,以免被hack导致丢分。

这里我用的哈希函数为H(x)=x(x+93)(x+117)mod(2321)H(x)=x(x+93)(x+117)\bmod(2^{32}-1),即A=93,B=117,P=2321A=93,B=117,P=2^{32}-1

#include <cstdio>
#include <unordered_set>
#define maxn 200005
using namespace std;

inline int read()
{
	char c;
	while((c = getchar()) < '0' || c > '9');
	int res = c ^ 48;
	while((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c ^ 48);
	return res;
}

unsigned suma[maxn], sumb[maxn];
inline void hread(unsigned* psum, int n)
{
	unordered_set<int> s;
	for(int i=1, x; i<=n; i++)
	{
		psum[i] = psum[i - 1];
		if(s.insert(x = read()).second)
			psum[i] += x * unsigned(x + 93) * unsigned(x + 117);
	}
}

int main()
{
	int n = read();
	hread(suma, n);
	hread(sumb, n);
	for(int q=read(); q--;)
		puts(suma[read()] == sumb[read()]? "Yes": "No");
	return 0;
}