2024/11/28 NOIP 模拟赛

数的拆分

Problem

给定 \(T\) 个正整数 \(a_{i}\),分别问每个 \(a_{i}\) 能否表示为 \(x_{1}^{y_{1}} \cdot x_{2}^{y_{2}}\) 的形式,其中 \(x_{1}, x_{2}\) 为正整数,\(y_{1}, y_{2}\) 为大于等于 \(2\) 的正整数。

对于所有评测用例,\(1 \leq T \leq 100000,1 \leq a_{i} \leq 10^{18}\)

Analysis

将原数分解质因数:\(a_i=p_1^{q_1}p_2^{q_2}p_3^{q_3}\dots p_n^{q_n}\)。当且仅当所有 \(q_i\geq 2\) 时满足题目条件。但是直接分解复杂度是错误的,考虑仅用 \(4000\) 以内的 \(q_i\) 来分解原数,当且仅当 剩下的数是平方数或立方数 时条件成立。

证明:剩下的数若满足条件,形式只能为 \(p^2\)\(p^3\)\(p^2q^2\) 三种(\(p,q\) 为质数且 \(p,q\geq 4000\))。因为 \(p^2q^3>10^{18}\) 则更大的幂次不成立。则只有平方数或立方数时成立。

Code

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

#define int long long
#define PII pair<int, int>
#define _for(i, a, b) for (int i = (a); i <= (b); i++)
#define _pfor(i, a, b) for (int i = (a); i >= (b); i--) 

const int N = 2e5 + 5;
int T, n, prime[N], tot; 

int check(int x) {
	_for(i, 2, x / i) if (x % i == 0) return 0;
	return 1;
}

int Sqr(int x) {
	int l = 1, r = 1e9;
	while (l < r) {
		int mid = (l + r + 1) >> 1;
		if (mid * mid <= x) l = mid;
		else r = mid - 1;
	}
	return (l * l == x);
}

int Cbr(int x) {
	int l = 1, r = 1e6;
	while (l < r) {
		int mid = (l + r + 1) >> 1;
		if (mid * mid * mid <= x) l = mid;
		else r = mid - 1;
	}
	return (l * l * l == x);
}

int read() {
	char c = ' ';
	int f = 1, x = 0;
	while (c < '0' || c > '9') {
		if (c == '-') f = -1;
		c = getchar();
	}
	while (c >= '0' && c <= '9') {
		x = x * 10 + c - '0';
		c = getchar();
	}
	return x * f;
}

void wr(int x) {
	if (x < 0) putchar('-'), x = -x;
	if (x > 9) wr(x / 10);
	putchar(x % 10 + '0');
}

signed main() {
//	freopen("number5.in", "r", stdin);
//	freopen("number5.out", "w", stdout);
	
	cin >> T;
	_for(i, 2, 4000) {
		if (check(i)) prime[++tot] = i;
	}
	while (T--) {
		n = read();
		int flg = 0;
		_for(i, 1, tot) {
			int cnt = 0;
			if (n % prime[i] == 0) {
				while (n % prime[i] == 0) cnt++, n /= prime[i];
				if (cnt == 1) flg = 1;
			}
		}
		if (!flg) {
			if (Cbr(n) || Sqr(n)) puts("yes");
			else puts("no");
		}
		else puts("no");
	}
}

P8863 「KDOI-03」构造数组

Problem

你现在有一个长度为 \(n\) 的数组 \(a\)。一开始,所有 \(a_i\) 均为 \(0\)。给出一个同样长度为 \(n\) 的目标数组 \(b\)。求有多少种方案,使得通过若干次以下操作,可以让 \(a\) 数组变成 \(b\)

  • 选出两个不同的下标 \(1\leq i<j\leq n\),并将 \(a_i\)\(a_j\) 同时增加 \(1\)

两种方案被称之为不同的,当且仅当存在一个 \(x\) 使得一种方案中第 \(x\) 次操作选择的两个下标 \((i,j)\) 与另一种方案中的不同。

\(n\le5~000\)\(\sum b_i\le30~000\) |

Analysis

首先这道题部分分是很足的。测试点 \(1\sim 5\) 输出 \(0\)\(1\)。测试点 \(6\sim 8\) 直接暴力 dfs。测试点 \(9\sim 14\) 满足 \(b_i=1\),可以找到规律。完成上述部分可以拿到 64 分。

正解:计算出操作次数 \(m=\dfrac{\sum _{i=1}^{n} b_i}{2}\)。把 \(m\) 次操作看成 \(m\) 个二元组,然后把 \(b_i\)\(i\) 放入这 \(m\) 个二元组中,二元组中不考虑顺序,也不能存在相同的数,问填入所有数的方案。考虑转化后的方案如何对应原来的方案,从填入数值层面考虑,发现考虑每个数值填入哪些二元组和方案一一对应:

例如 \(n=3,b=[1,2,3]\)

  • \([(1,3),(2,3),(2,3)]⇔[(1,\{1\}),(2,\{2,3\}),(3,\{1,2,3\})]\)
  • \([(2,3),(1,3),(2,3)]⇔[(1,\{2\}),(2,\{1,3\}),(3,\{1,2,3\})]\)
  • \([(2,3),(2,3),(1,3)]⇔[(1,\{3\}),(2,\{1,2\}),(3,\{1,2,3\})]\)

有了上述思路,可以定义 \(f(i,m_1,m_2)\) 表示填完前 \(i\) 个数值,存在 \(m_1\) 个二元组只有一个数,存在 \(m_2\) 个二元组只有两个数的方案数。可以推算一个数都没填的二元组数量 \(m_0=m-m_1-m_2\)。转移:

\[f(i+1,m_1-k+b_{i+1}-k,m_2+k)=\sum f(i,m_1,m_2) \binom{m_1}{k} \binom{m_0}{b_{i+1}-k} \]

复杂度 \(\mathcal O((n+m)m^2)\)

但是注意到考虑前 \(i\) 数值的 \(m_1=\sum_{j=1}^{i} b_j-2m_2\)。则可以直接删除 \(m_1\) 这一维。

\[f(i+1,m_2+k)=\sum f(i,m_2) \binom{m_1}{k}\binom{m_0}{b_{i+1}-k}(m_1,m_0\geq 0) \]

复杂度 \(\mathcal O((n+m)m)\)。因为内层的 \(k\) 限制于 \(b_i\),则总枚举量为 \(m\)

Code

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

#define PII pair<int, int>
#define _for(i, a, b) for (int i = (a); i <= (b); i++)
#define _pfor(i, a, b) for (int i = (a); i >= (b); i--)
#define int long long

const int N = 5000 + 5, M = 30010, mod = 998244353;

int n, m, a[N], f[2][M], sum[N], fac[M], invfac[M];

int qpow(int a, int k, int p) {
	int res = 1;
	while (k) {
		if (k & 1) res = res * a % p;
		a = a * a % p;
		k >>= 1;
	}
	return res;
}

void init() {
	fac[0] = invfac[0] = 1;
	_for(i, 1, M - 5) {
		fac[i] = fac[i - 1] * i % mod;
		invfac[i] = invfac[i - 1] * qpow(i, mod - 2, mod) % mod;
	}
}

int C(int n, int m) {
	if (n < m || n < 0 || m < 0) return 0;
	return fac[n] * invfac[n - m] % mod * invfac[m] % mod;
}

signed main() {
	cin >> n; init();
	_for(i, 1, n) cin >> a[i], m += a[i], sum[i] = sum[i - 1] + a[i];
	if (m % 2 == 1) puts("0"), exit(0);
	m /= 2;
	f[0][0] = 1;
	_for(i, 0, n - 1) {
		_for(m2, 0, m) {
			int m1 = sum[i] - 2 * m2, m0 = m - m1 - m2;
			if (m0 < 0 || m1 < 0) continue;
			_for(k, 0, a[i + 1]) {
				if (m2 + k <= m) f[(i + 1) & 1][m2 + k] = (f[(i + 1) & 1][m2 + k] + f[i & 1][m2] * C(m1, k) % mod * C(m0, a[i + 1] - k) % mod) % mod;
			}
		}
		memset(f[(i + 2) & 1], 0, sizeof f[(i + 2) & 1]);
	}
	cout << f[n & 1][m] << endl;
}
posted @ 2024-11-28 21:59  Otue  阅读(40)  评论(0)    收藏  举报