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\)。转移:
复杂度 \(\mathcal O((n+m)m^2)\)。
但是注意到考虑前 \(i\) 数值的 \(m_1=\sum_{j=1}^{i} b_j-2m_2\)。则可以直接删除 \(m_1\) 这一维。
复杂度 \(\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;
}

浙公网安备 33010602011771号