2024.02.18 测试

成功和鸟哥一起拿下新概念 rk1,特此纪念。

T1 家庭作业

link: gxyzOJ #3593

Description

小 y 最近收到一个家庭作业,计算 \(A\)\(B\) 的最大公约数。由于这两个数太大了,我们给出了 \(n\) 个数,它们的乘积是 \(A\),给出 \(m\) 个数,它们的乘积是 \(B\)

输出这个最大公约数对 \(1000000000\) 的值。

Solution

Train of Thought 1

分解质因数。

\(\gcd\) 的定义入手,先做求质数的预处理,再对 \(a,b\) 两个数组做质因数分解,取相同的。

时间复杂度由于求质因数的方法而异,在 \(O(nm) \sim O(n^2m)\) 之间( \(O(n)\)、埃氏筛求质数 \(O(n \log n)\)、暴力求质数 \(O(n^2)\)

code from @wangsiqi2010916 (%%%)

#include <cstdio>
#define ll long long
using namespace std;
int n, m, a, b, cnt[50005], p[50005], p1[1005], mod = 1000000000, idx, cnt2[50005];
bool vis[50005];
ll ans = 1;
void get_prime() {
	for (int i = 2; i <= 50000; i++) {
		if (!vis[i]) p[++idx] = i;
		for (int j = 1; i * p[j] <= n; j++) {
			vis[i * p[j]] = 1;
			if (i % p[j] == 0) break;
		}
	}
}
int main() {
	get_prime();
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%d", &a);
		for (int j = 1; j <= idx; j++) {
			while (a % p[j] == 0) {
				a /= p[j];
				cnt[j]++;
			}
		}
		if (a > 1) p1[i] = a;
	}
	scanf("%d", &m);
	for (int i = 1; i <= m; i++) {
		scanf("%d", &b);
		for (int j = 1; j <= idx; j++) {
			while (b % p[j] == 0) {
				b /= p[j];
				if (cnt2[j] < cnt[j]) {
					cnt2[j]++;
					ans = ans * p[j] % mod;
				}
			}
		}
		if (b > 1) {
			for (int j = 1; j <= n; j++) {
				if (b == p1[j]) {
					ans = ans * p1[j] % mod;
					p1[j] = 0;
					break;
				}
			}
		}
	}
	printf("%lld", ans % mod);
	return 0;
}

Train of Thought 2

分别求每两个数之间的 \(\gcd\) 值,每个数取后除去 \(\gcd\) 值,\(ans = \prod_{i = 1}^{n} \prod_{j = 1}^{m} \gcd(a_i, b_j)\)

时间复杂度 \(O(mn)\)

code

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1005, mod = 1000000000;
int n, m, ans = 1;
int a[N], b[N];
int gcd(int a, int b) {
	if (a % b == 0) return b;
	return gcd(b, a % b);
}
signed main() {
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i];
	cin >> m;
	for (int i = 1; i <= m; i++) cin >> b[i];
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			int t = gcd(a[i], b[j]);
			ans = ans * t % mod;
			a[i] /= t, b[j] /= t;
		}
	}
	cout << ans << '\n';
	return 0;
}

T2 距离之和

link: gxyzOJ #3594
link: Luogu P7745

更详细的题解:link

Description

想象一个机器人位于二维空间。初始时,机器人在 \((0,0)\)。有4个命令 S,J,I,Z

具体的,如果机器人在 \((x, y)\),在收到 S 命令之后,移动到 \((x, y + 1)\),收到 J 之后,移动到 \((x, y - 1)\)I 命令之后移动到 \((x + 1, y)\)Z 命令之后移动到 \((x - 1, y)\)

在这个二维空间有 \(n\) 个固定的点,在每个命令之后,每个固定点会计算自己与机器人的曼哈顿距离,然后返回这些距离的总和。

ps:两个点 \((x1, y1)\)\((x2, y2\) 的曼哈顿距离为 \(|x1 - x2| + |y1 - y2|\)

Solution

40 pts

每次移动后暴力求出每个点的哈曼顿距离,相加输出即可。

code

#include <bits/stdc++.h>
#define N 100005
#define int long long
using namespace std;
namespace xycyx {
	int n, m;
	int hmd[N];
	int X[N], Y[N];
	char c;
	int botx = 0, boty = 0;
	int get_hmd(int X1, int Y1) {
		return abs(X1 - botx) + abs(Y1 - boty);
	}
	void solve() {
		ios::sync_with_stdio();
		cin.tie(0);
		cin >> n >> m;
		for (int i = 1; i <= n; i++) cin >> X[i] >> Y[i];
		for (int i = 1; i <= m; i++) {
			cin >> c;
			if (c == 'S') boty++;
			if (c == 'J') boty--;
			if (c == 'I') botx++;
			if (c == 'Z') botx--;
			int ans = 0;
			for (int i = 1; i <= n; i++)
				ans += get_hmd(X[i], Y[i]);
			cout << ans << '\n';
		}
	}
}
signed main() {
	xycyx::solve();
	return 0;
} //40pts

100 pts

鉴于数据范围,时间复杂度应为 \(O(m)\),故每次查询的时间复杂度都为 \(O(1)\)。考虑二分。

对于两条坐标轴,每次移动后分别二分求出有多少个点小于当前机器人的位置,多少点大于当前机器人的位置即可。

可以使用 upper_boundlower_bound

code

注意先排序后再二分查找。

#include <bits/stdc++.h>
#define N 100000
#define M 300000
#define ll long long
using namespace std;
namespace cyxyc {
	int n, m;
	int X1, Y1, X2, Y2, X, Y;
	ll ans = 0;
	int x[N + 5], y[N + 5];
	char str[M + 5];
	void solve() {
		ios::sync_with_stdio();
		cin.tie(0);
		cin >> n >> m;
		for (int i = 1; i <= n; i++) {
			cin >> x[i] >> y[i];
			ans += abs(x[i]) + abs(y[i]);
		}
		cin >> str + 1;
		sort(x + 1, x + 1 + n), sort(y + 1, y + 1 + n);
		X1 = upper_bound(x + 1, x + 1 + n, X) - (x + 1);
		Y1 = upper_bound(y + 1, y + 1 + n, Y) - (y + 1);
		X2 = lower_bound(x + 1, x + 1 + n, X) - (x + 1);
		Y2 = lower_bound(y + 1, y + 1 + n, Y) - (y + 1);
		for (int i = 1; i <= m; i++) {
			if (str[i] == 'S') {
				ans += 2 * Y1 - n;
				Y++;
				Y1 = upper_bound(y + 1, y + 1 + n, Y) - (y + 1);
				Y2 = lower_bound(y + 1, y + 1 + n, Y) - (y + 1);
			}
			if (str[i] == 'J') {
				ans += n - 2 * Y2;
				Y--;
				Y1 = upper_bound(y + 1, y + 1 + n, Y) - (y + 1);
				Y2 = lower_bound(y + 1, y + 1 + n, Y) - (y + 1);
			}
			if (str[i] == 'I') {
				ans += 2 * X1 - n;
				X++;
				X1 = upper_bound(x + 1, x + 1 + n, X) - (x + 1);
				X2 = lower_bound(x + 1, x + 1 + n, X) - (x + 1);
			}
			if (str[i] == 'Z') {
				ans += n - 2 * X2;
				X--;
				X1 = upper_bound(x + 1, x + 1 + n, X) - (x + 1);
				X2 = lower_bound(x + 1, x + 1 + n, X) - (x + 1);
			}
			printf("%lld\n", ans);
		}
	}
}
int main() {
	cyxyc::solve();
	return 0;
}

T3 country

link: gxyzOJ #3596
link: BZOJ2061

Description

gaoxin 神犇频繁的在发言中表现对伟大,光荣,正确的xx的热爱,我们可以做如下定义:

A = 伟大,光荣,正确的

B = xx

C = 引领我们向前

赞美祖国 = ABC

拼命赞美祖国=赞美祖国 \(\times 10\)

gaoxin 的发言=拼命赞美祖国 \(\times 100\)

显然这个定义必须是无环的。

WJMZBMR感到十分的有压力,他好不容易数出了某个字串的出现次数。

某天他打开电视,发现某人的发言有同样的结构。他很无语,想知道某些字串出现的次数。你能帮帮他吗?


一些定义:

为了简化期间,在输入中用英文表示。

  • 字符串:由小写字母组成,如 \(a\)
  • 字串名:一定是大写字母,如 \(A\)

那么上面的系统可以写成:

  • \(\text{A=greatglorycorrect}\)
  • \(\text{B=xx}\)
  • \(\text{C=leadusgo}\)
  • \(\text{D=ABC}\)
  • \(\text{E=DDDDdjh}\)
  • \(\text{F=EEEEEgoodbye}\)

同时存在一个母字串名,他就是某人的发言。

Solution

前言

不会。还没改出来。

%%% dingzibo_______ dalao's solution
Want to find dalao dzb? Click here: dingzibo_______

%%%,截至2024年2月18日20点22分,已有 \(7\) 位 dalao 改出此题,他(她)们分别是 @dingzibo_______,@Dyc780511,@shaanxi_liuyuhe_yyyy,@wangsiqi2010916,@No0Chenquanlin,@STAR_light_,@zhangxy__hp。快来 % 他(她)们!

now

OK,其实早就改出来了,忘更新了。
这个:link

简单来说,就是 KMP + DFS + 记忆化搜索
注意有几个点:

  1. 两个字符串合并后,前一个串的末尾可能和后一个串的开头组成一个符合要求的字符串;
  2. 同一个字符可能会被重复计算。

code

#include <bits/stdc++.h>
#define maxn 30
#define maxm 100005
#define MOD 10000
using namespace std;
namespace bydcql_bxy {
	int n, m;
	int a[maxn][maxm], len[maxn];
	char tp[maxn], lentp, t[maxm];
	int nxt[maxm];
	int dp[maxn][maxm], pos[maxn][maxm];
	//dp[i][j]表示字符串i,当前还未匹配时末尾已经与模板串匹配了j位,串i里包含模式串的个数
	//pos[i][j]表示匹配结束后KMP指针的位置
	
	void get_nxt(char* S, int n, int* nxt) {
		nxt[1] = 0;
		for (int i = 2, j = 0; i <= n; i++) {
			while (j && S[j + 1] != S[i]) j = nxt[j];
			if (S[j + 1] == S[i]) j++;
			nxt[i] = j;
		}
	}
	
	int dfs(int i, int j) {
		if (dp[i][j] != -1) return dp[i][j];
		dp[i][j] = 0;
		int x = j;
		for (int k = 1; k <= len[i]; k++) {
			if (a[i][k] >= 'A' && a[i][k] <= 'Z') {
				int id = a[i][k] - 'A' + 1;
				dp[i][j] = (dp[i][j] + dfs(id, x) % MOD + MOD) % MOD;
				x = pos[id][x];
			} else {
				int p = x;
				while (p && tp[p + 1] != a[i][k]) p = nxt[p];
				if (tp[p + 1] == a[i][k]) p++;
				x = p;
				if (x == lentp) dp[i][j] = ((dp[i][j] + 1) % MOD + MOD) % MOD;
			}
		}
		pos[i][j] = x;
		return dp[i][j];
	}
	
	void solve() {
		ios::sync_with_stdio(false);
		cin.tie(NULL);
		cin >> n >> t + 1;
		m = t[1] - 'A' + 1;
		for (int i = 1; i <= n; i++) {
			cin >> t + 1;
			int l = strlen(t + 1);
			for (int j = 3; j <= l; j++) a[t[1] - 'A' + 1][j - 2] = t[j];
			len[i] = l - 2;
		}
		cin >> tp + 1;
		lentp = strlen(tp + 1);
		get_nxt(tp, lentp, nxt);
		memset(dp, -1, sizeof(dp));
		dfs(m, 0);
		cout << dp[m][0] << '\n';
	}
}

int main() {
	bydcql_bxy::solve();
	return 0;
}

T4 太空飞船

link: gxyzOJ #3597
link: Luogu U407608

Solution

40 pts

直接 \(O(n^4)\) 的暴力即可。

code from @Laoshan_PLUS (%%%)

#include <bits/stdc++.h>
#define int long long
using namespace std;

constexpr int MAXN = 10005;
int n, m, a[MAXN];
long long ans;

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(nullptr);
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i];
	if (n < 4) {
		cout << 0 << '\n';
		return 0;
	}
	sort(a + 1, a + n + 1);
	m = unique(a + 1, a + n + 1) - a - 1;
	for (int i = 1; i <= m; i++)
		for (int j = i + 1; j <= m; j++)
			for (int k = j + 1; k <= m; k++)
				for (int l = k + 1; l <= m; l++)
					if (__gcd(a[i], __gcd(a[j], __gcd(a[k], a[l]))) == 1)
						ans++;
	cout << ans << '\n';
	
	return 0;
}

60 pts

考虑动态规划。设 \(f_{i, j, k}\) 表示选取到第 \(i\) 个数,已选取的数的最大公约数为 \(j\),选取的个数为 \(k(0 \le k \le 4)\) 的总方案数。转移方程如下:

\[f_{i, \gcd(a[i],j), k} += f_{i-1, j, k-1} \]

则最终答案为 \(f_{n, 1, 4}\),其中第一维 \(i\) 可用滚动数组优化。

100 pts

容斥原理(莫比乌斯反演? 不会)。

易得,\(ans =\) 总方案数 \(-\) 不可行方案。

\(num_i\) 表示集合中是 \(i\) 倍数的元素的个数,故 \(ans = C_n^4 - \sum{C_{num_i}^4}\)

但是显然的,\(num_6\) 已经在 \(num_2\)\(num_3\) 被统计过,所以应改为:

\[ans = C_n^4 - C_{num_2}^4 - C_{num_3}^4 - C_{num_5}^4 - \cdots + C_{num_{2 \times 3}}^4 + C_{num_{2 \times 5}}^4 + \cdots + C_{num_{2 \times 3 \times 5}} + \cdots - \cdots \cdots \]

化简,可得:

\[ans = C_n^4 + \sum{C_{num_i}^4} \times (-1)^{cnt_i} \]

其中 \(cnt_i\) 表示 \(i\) 所包含的质数的数量。

code

#include <bits/stdc++.h>
#define int long long
#define N 10000
using namespace std;
namespace yydz_wcnm {
	int n;
	int a[N + 5], num[N + 5];
	int ans, C[N + 5];
	void init() {
		for (int i = 4; i <= N; i++)
			C[i] = i * (i - 1) * (i - 2) * (i - 3) / 24;
		ans = C[n];
	}
	void solve() {
		ios::sync_with_stdio();
		cin.tie(0);
		cin >> n;
		init();
		for (int i = 1; i <= n; i++) {
			cin >> a[i];
			num[a[i]] = 1;
		}
		for (int i = 1; i <= n; i++) {
			for (int j = 2; j * j <= a[i]; j++) {
				if (a[i] % j == 0) {
					num[j]++;
					if (j * j != a[i]) num[a[i] / j]++;
				}
			}
		}
		for (int i = 1; i <= N; i++) {
			bool flag = 0;
			for (int j = 2; j * j <= i; j++) {
				if (i % j == 0 && i / j % j == 0) {
					flag = 1;
					break;
				}
			}
			if (flag) continue;
			int cnt = 0, x = i;
			for (int j = 2; j * j <= x; j++)
				if (x % j == 0) {
					cnt++;
					while (x % j == 0) x /= j;
				}
			if (x != 1) cnt++;
			if (cnt & 1) ans -= C[num[i]];
			else ans += C[num[i]];
		}
		cout << ans << '\n';
	}
}
signed main() {
	yydz_wcnm::solve();
	return 0;
}

也可以预处理质数后再求求组合数。

code from @wangsiqi2010916 (%%% \(\times 2\))

#include <cstdio>
#define ll long long
using namespace std;
int n, p[10000], m, num[10005], vis[10005], cnt[10005];
ll ans, x[10005];
void get_prime() {
	for (int i = 2; i <= 10000; i++) {
		if (!vis[i]) {
			p[++m] = i;
			num[i] = 1;
		}
		for (int j = 1; i * p[j] <= 10000; j++) {
			if (i % p[j] && vis[i] != 2) {
				vis[i * p[j]] = 1;
				num[i * p[j]] = num[i] + 1;
			} else {
				vis[i * p[j]] = 2;
			}
			if (i % p[j] == 0) break;
		}
	}
}
int main() {
	get_prime();
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		x[i] = 1ll * i * (i - 1) * (i - 2) * (i - 3) / 24;
	}
	if (n < 4) {
		printf("0");
		return 0;
	}
	ans = 1ll * x[n];
	for (int i = 1; i <= n; i++) {
		int a;
		scanf("%d", &a);
		for (int j = 1; j * j <= a; j++) {
			if (a % j == 0) {
				cnt[j]++;
				if (a / j != j) cnt[a / j]++;
			}
		}
	}
	for (int i = 2; i <= 10000; i++) {
		if (vis[i] == 2 || cnt[i] < 4) continue;
		if (num[i] % 2) ans = ans - x[cnt[i]];
		else ans = ans + x[cnt[i]];
	}
	printf("%lld", ans);
	return 0;
}

总结

疫严顶真,我测你码

秽土鼬一个。暴力打满,rk1(正着数的)到手。

不要被预处理冲昏了小脑。

看题在再认真仔细一些,多想,多试。注意末尾等特殊情况。

好好理解题意,手推样例。

不会的题,暴力暴力暴力还是暴力;会的题写正解之前暴力暴力暴力还是暴力。

posted @ 2024-02-27 20:44  Foiled  阅读(27)  评论(0编辑  收藏  举报