CF思维体操

StkOvflow / 2023-05-03 / 原文

Plus and Multiply

Luogu
CF

题意简述:

\(\qquad\)给定 \(n,\) \(a,\) \(b\),要求判断 \(n\) 能不能由 \(a\) 通过若干次 \(\times a\) 或者 \(+b\)得到。

解题思路:

\(\qquad\)我们首先应该找到能由上述操作得到的数的普遍形式。

\[对于n = a^x+by (x, y\in N),n是满足题意的数 \]

\(\qquad\)我们可以证明一下这个形式可行,对于第一个数 \(1\),显然可以表示成 \(a^0+0·b\),所以对于 \(1\) 是成立的。如果 \(a^x+by\) 是成立的,那么进行 \(\times a\) 操作后变为 \(a^{x+1}+by\),进行 \(+b\) 操作后变为 \(a^x+b(y+1)\),形式仍然不变,所以我们这个形式是具有普遍可行性的。

\[n = a^x + by\Rightarrow n - a^x = by\Rightarrow n-a^x\equiv 0\ (mod\ b) \]

\(\qquad\) 因此我们只需要依次枚举 \(x\),并且判定即可,另外要注意的是 \(a = 1\) 要特判,否则 \(TLE\)

#include <bits/stdc++.h>
#define pb emplace_back

using namespace std;
using PII = pair<int, int>;

int main() {
	int tc;
	scanf("%d", &tc);
	while (tc -- ) {
		int n, a, b;
		scanf("%d%d%d", &n, &a, &b);
		if (a == 1) 
			puts((n - 1) % b == 0 ? "Yes" : "No");
		else {
			int flag = 0;
			for (long long i = 1; !flag && i <= n; i *= a) 
				if ((n - i) % b == 0) flag = 1;
			puts(flag ? "Yes" : "No");
		}
	}
	return 0;
}

Ehab the Xorcist

Luogu
CF

题意简述

\(\qquad\)给定两个数 \(u,\ v\),要求构造一个数列使得数列的异或和为 \(u\), 数列的数值和为 \(v\)

解题思路

\(\qquad\)定义 \(\Delta = v - u\)从异或的角度出发,因为异或是二进制下的不进位加法,所以异或和一定不能大于数值和。所以当 \(\Delta < 0\)的时候,无解。另外这里有一个很重要的性质,即数列的异或和与数值和的奇偶性相同。证明如下:

\[a\operatorname{xor} b + 2(a\&b) = a + b\Rightarrow a+b-a\operatorname{xor} b = 2(a\&b) \]

\(\qquad\)因为 \(2(a\&b)\) 是偶数,所以两个数的异或和与数值和奇偶性相同,以此类推即可.
\(\qquad\)所以我们可以对 \(\Delta\) 进行一下分类讨论:

\[1.\Delta < 0, 无解 \]

\[2.\Delta = 0, 若u = 0, 则目标数列为\{0\},否则为\{u\} \]

\[3.\Delta > 0 且\Delta为奇数,无解 \]

\[4.\Delta > 0且\Delta\operatorname{xor}u = u,则目标数列为\{\Delta, u\} \]

\[5.\Delta > 0且\Delta\operatorname{xor}u \neq u \]

\(5\) 种情况需要一定的思考,因为 \(\Delta\) 一定是偶数,两个相同的数异或和为 \(0\), 一个数与 \(0\) 异或得本身,那不妨把 \(\Delta\) 拆成两块,每块为 \(x\),目标数列为\(\{x,x,u\}\)

#include <bits/stdc++.h>
#define pb emplace_back

using namespace std;
using PII = pair<int, int>;
using LL = long long;

int main() {
	LL u, v;
	scanf("%lld%lld", &u, &v);
	LL delta = v - u;
	if (delta & 1 || delta < 0) puts("-1");
	else {
		if (delta == 0) {
			if (u == 0) puts("0");
			else printf("1\n%lld\n", u);
		}
		else {
			LL x = delta >> 1;
			if ((u ^ delta) != u) {
				if ((x ^ (x + u)) != u) {
					puts("3");
					printf("%lld %lld %lld\n", x, x, u);
				} else {
					puts("2");
					printf("%lld %lld\n", x + u, x);
				}
			} else {
				puts("2");
				printf("%lld %lld\n", delta, u);
			}
		}
	}
	return 0;
}

Orac and Medians

Luogu
CF

题意简述

给定一个序列,每次可以将一段区间修改为区间中位数,问能否将整个序列全部改为 \(k\)

解题思路

\(\qquad\)我们可以先看看我们的操作有什么性质:

\[1.相邻的两个数会变成比较小的那个数 \]

\[2.相邻的三个数,如果有两个相等,那都会变成相等的 \]

\(\qquad\)由这两条性质可以推出以下几条:

\[1.如果有相邻的两个 k,那么很容易将全部都变成k \]

\[2.如果相邻的两个数分别是k和一个大于k的数,也可以全部推成k \]

\[3.如果三个数里面有两个>=k,那么染色后一定全部>=k,此时只要有k,就可以全推成k \]

以上条件只要满足任意一个,就可以成功,否则不行。
逐个判定就行了。

#include <bits/stdc++.h>
#define pb emplace_back

using namespace std;
using PII = pair<int, int>;
using LL = long long;

int a[100010];

int main() {
	int tc;
	scanf("%d", &tc);
	while (tc -- ) {
		int n, k;
		scanf("%d%d", &n, &k);
		for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
		if (n == 1 && a[n] != k) puts("no");
		else {
			if (n == 1) puts("yes");
			else {
				int flag = 0, has = 0;
				for (int i = 1; i <= n; i ++ )
					has |= a[i] == k;
				if (!has) puts("no");
				else {
					for (int i = 1; i < n && !flag; i ++ ) 
						flag |= (a[i] >= k && a[i + 1] >= k);
					if (flag) puts("yes");
					else {
						for (int i = 1; i < n - 1 && !flag; i ++ )
							flag |= (a[i] >= k && a[i + 2] >= k);
						if (flag) puts("yes");
						else puts("no");
					}
				}
				
			}
		}
	}
	return 0;
}