Codeforces Round 985 div1+2 个人题解(A~E)
Dashboard - Refact.ai Match 1 (Codeforces Round 985) - Codeforces
火车头
#include <bits/stdc++.h>
using namespace std;
#define ft first
#define sd second
#define yes cout << "yes\n"
#define no cout << "no\n"
#define Yes cout << "Yes\n"
#define No cout << "No\n"
#define YES cout << "YES\n"
#define NO cout << "NO\n"
#define pb push_back
#define eb emplace_back
#define all(x) x.begin(), x.end()
#define all1(x) x.begin() + 1, x.end()
#define unq_all(x) x.erase(unique(all(x)), x.end())
#define unq_all1(x) x.erase(unique(all1(x)), x.end())
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fLL
#define RED cout << "\033[91m" // 红色
#define GREEN cout << "\033[92m" // 绿色
#define YELLOW cout << "\033[93m" // 蓝色
#define BLUE cout << "\033[94m" // 品红
#define MAGENTA cout << "\033[95m" // 青色
#define CYAN cout << "\033[96m" // 青色
#define RESET cout << "\033[0m" // 重置
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
// typedef __int128_t i128;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<ll, int> pli;
typedef pair<string, ll> psl;
typedef tuple<int, int, int> ti3;
typedef tuple<ll, ll, ll> tl3;
typedef tuple<ld, ld, ld> tld3;
typedef vector<bool> vb;
typedef vector<int> vi;
typedef vector<ll> vl;
typedef vector<string> vs;
typedef vector<vi> vvi;
typedef vector<vl> vvl;
// std::mt19937_64 rng(std::chrono::steady_clock::now().time_since_epoch().count());
template <typename T>
inline T read()
{
T x = 0;
int y = 1;
char ch = getchar();
while (ch > '9' || ch < '0')
{
if (ch == '-')
y = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * y;
}
template <typename T>
inline void write(T x)
{
if (x < 0)
{
putchar('-');
x = -x;
}
if (x >= 10)
{
write(x / 10);
}
putchar(x % 10 + '0');
}
/*#####################################BEGIN#####################################*/
void solve()
{
}
int main()
{
ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
// freopen("test.in", "r", stdin);
// freopen("test.out", "w", stdout);
int _ = 1;
std::cin >> _;
while (_--)
{
solve();
}
return 0;
}
/*######################################END######################################*/
// 链接:
A. Set
给你一个正整数 k k k 和一个由 l l l 至 r r r (含)的所有整数组成的集合 S S S。
您可以执行以下两步运算的任意次数(可能为零):
首先,从集合
S
S
S 中选择一个数字
x
x
x,使得
S
S
S 中至少有
k
k
k 个
x
x
x 的倍数(包括
x
x
x 本身);
然后,从
S
S
S 中删除
x
x
x (注意没有删除任何其他内容)。
求可以进行的最大操作数。
输入
每个测试包含多个测试用例。输入的第一行包含一个整数
t
t
t (
1
≤
t
≤
1
0
4
1 \leq t \leq 10^4
1≤t≤104)—— 测试用例的数量。测试用例说明如下。
每个测试用例的唯一一行包含三个整数 l l l、 r r r 和 k k k ( 1 ≤ l ≤ r ≤ 1 0 9 1 \leq l \leq r \leq 10^9 1≤l≤r≤109, 1 ≤ k ≤ r − l + 1 1 \leq k \leq r - l + 1 1≤k≤r−l+1)—— 最小整数 S S S、最大整数 S S S 和参数 k k k。
输出
对于每个测试用例,输出一个整数——可执行的最大操作数。
示例
输入
8
3 9 2
4 9 1
7 9 2
2 10 2
154 220 2
147 294 2
998 24435 3
1 1000000000 2
输出
2
6
0
4
0
1
7148
500000000
提示
在第一个测试用例中,初始时
S
=
{
3
,
4
,
5
,
6
,
7
,
8
,
9
}
S=\{3,4,5,6,7,8,9\}
S={3,4,5,6,7,8,9}。一个可能的最佳操作序列是:
选择
x
=
4
x=4
x=4 进行第一次操作,因为
S
S
S 中有两个
4
4
4 的倍数:
4
4
4 和
8
8
8。
S
S
S 变为
{
3
,
5
,
6
,
7
,
8
,
9
}
\{3,5,6,7,8,9\}
{3,5,6,7,8,9};
选择
x
=
3
x=3
x=3 进行第二次操作,因为
S
S
S 中有三个
3
3
3 的倍数:
3
3
3、
6
6
6 和
9
9
9。
S
S
S 变为
{
5
,
6
,
7
,
8
,
9
}
\{5,6,7,8,9\}
{5,6,7,8,9}。
在第二个测试用例中,初始时 S = { 4 , 5 , 6 , 7 , 8 , 9 } S=\{4,5,6,7,8,9\} S={4,5,6,7,8,9}。一个可能的最佳操作序列是:
选择
x
=
5
x=5
x=5,
S
S
S 变为
{
4
,
6
,
7
,
8
,
9
}
\{4,6,7,8,9\}
{4,6,7,8,9};
选择
x
=
6
x=6
x=6,
S
S
S 变为
{
4
,
7
,
8
,
9
}
\{4,7,8,9\}
{4,7,8,9};
选择
x
=
4
x=4
x=4,
S
S
S 变为
{
7
,
8
,
9
}
\{7,8,9\}
{7,8,9};
选择
x
=
8
x=8
x=8,
S
S
S 变为
{
7
,
9
}
\{7,9\}
{7,9};
选择
x
=
7
x=7
x=7,
S
S
S 变为
{
9
}
\{9\}
{9};
选择
x
=
9
x=9
x=9,
S
S
S 变为空集。
在第三个测试用例中,初始时 S = { 7 , 8 , 9 } S=\{7,8,9\} S={7,8,9}。对于 S S S 中的每个 x x x,无法找到除 x x x 本身以外的其他 x x x 的倍数。由于 k = 2 k=2 k=2,您无法进行任何操作。
在第四个测试用例中,初始时 S = { 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 } S=\{2,3,4,5,6,7,8,9,10\} S={2,3,4,5,6,7,8,9,10}。一个可能的最佳操作序列是:
选择
x
=
2
x=2
x=2,
S
S
S 变为
{
3
,
4
,
5
,
6
,
7
,
8
,
9
,
10
}
\{3,4,5,6,7,8,9,10\}
{3,4,5,6,7,8,9,10};
选择
x
=
4
x=4
x=4,
S
S
S 变为
{
3
,
5
,
6
,
7
,
8
,
9
,
10
}
\{3,5,6,7,8,9,10\}
{3,5,6,7,8,9,10};
选择
x
=
3
x=3
x=3,
S
S
S 变为
{
5
,
6
,
7
,
8
,
9
,
10
}
\{5,6,7,8,9,10\}
{5,6,7,8,9,10};
选择
x
=
5
x=5
x=5,
S
S
S 变为
{
6
,
7
,
8
,
9
,
10
}
\{6,7,8,9,10\}
{6,7,8,9,10}。
解题思路
对于一个数 n n n,我们能构造出的最大的有 k k k 个 x x x 的倍数的 x x x为 ⌊ n k ⌋ \lfloor \frac{n}{k}\rfloor ⌊kn⌋。因此,答案为 max ( 0 , ⌊ r k ⌋ − l + 1 ) \max(0,\lfloor \frac{r}{k}\rfloor-l+1) max(0,⌊kr⌋−l+1)
实现代码
void solve()
{
ll l, r, k;
cin >> l >> r >> k;
cout << max(0ll, r / k - l + 1) << "\n";
}
B. Replacement
您有一个长度为 n n n 的二进制字符串 s s s,而艾瑞丝会给出另一个长度为 n − 1 n-1 n−1 的二进制字符串 r r r。
艾瑞丝将和你玩一个游戏。在游戏过程中,你将对 s s s 执行 n − 1 n-1 n−1 操作。在第 i i i 次操作中( 1 ≤ i ≤ n − 1 1 \leq i \leq n-1 1≤i≤n−1):
首先,你要选择一个索引
k
k
k,使得
1
≤
k
≤
∣
s
∣
−
1
1 \leq k \leq |s| - 1
1≤k≤∣s∣−1 且
s
k
≠
s
k
+
1
s_k \neq s_{k+1}
sk=sk+1。如果无法选择这样的索引,那么就输了;
然后,将
s
k
s
k
+
1
s_k s_{k+1}
sksk+1 替换为
r
i
r_i
ri。请注意,这会使
s
s
s 的长度减少
1
1
1。
如果所有的
n
−
1
n-1
n−1 操作都成功执行,那么你就赢了。
请判断你是否有可能赢得这个游戏。
输入
每个测试包含多个测试用例。输入的第一行包含一个整数
t
t
t (
1
≤
t
≤
1
0
4
1 \leq t \leq 10^4
1≤t≤104)—— 测试用例的数量。测试用例说明如下。
每个测试用例的第一行包含一个整数 n n n ( 2 ≤ n ≤ 1 0 5 2 \leq n \leq 10^5 2≤n≤105)—— s s s 的长度。
第二行包含长度为 n n n ( s i = 0 s_i = 0 si=0 或 1 1 1)的二进制字符串 s s s。
第三行包含长度为 n − 1 n-1 n−1 ( r i = 0 r_i = 0 ri=0 或 1 1 1)的二进制字符串 r r r。
保证所有测试用例中 n n n 的总和不超过 1 0 5 10^5 105。
输出
对于每个测试用例,如果能赢得游戏,则打印 “YES”(不带引号),否则打印 “NO”(不带引号)。
您可以用任何大小写(大写或小写)输出答案。例如,字符串 “yEs”、“yes”、“Yes” 和 “YES” 将被识别为肯定回答。
示例
输入
6
2
11
0
2
01
1
4
1101
001
6
111110
10000
6
010010
11010
8
10010010
0010010
输出
NO
YES
YES
NO
YES
NO
提示
在第一个测试用例中,您无法执行第一次操作。因此,您输了游戏。
在第二个测试用例中,您可以选择 k = 1 k=1 k=1 进行唯一的操作,之后 s s s 变为 1 1 1。因此,您赢得了游戏。
在第三个测试用例中,您可以执行以下操作: 110 → r 1 = 0101 → r 2 = 010 → r 3 = 11 110 \to r_1 = 0101 \to r_2 = 010 \to r_3 = 11 110→r1=0101→r2=010→r3=11。
解题思路
注意到我们每次是选择一个 01 01 01或者 10 10 10来进行替换,使其留下 1 1 1或者 0 0 0,因此,每个替换都等价于删除另一种数字,例如替换为 1 1 1等价于删除一个 0 0 0,所以,我们可以维护 0 0 0和 1 1 1的数量,如果在最后一次删除前 0 0 0和 1 1 1的数量归零则游戏输了。
实现代码
void solve()
{
int n;
string s, r;
cin >> n >> s >> r;
int cnt0 = 0;
int cnt1 = 0;
for (auto c : s)
{
if (c == '1')
cnt1++;
else
cnt0++;
}
if (cnt0 == 0 || cnt1 == 0)
{
NO;
return;
}
for (int i = 0; i < n - 1; i++)
{
if (r[i] == '1')
cnt0--;
else
cnt1--;
if (i == n - 2)
continue;
if (cnt0 == 0 || cnt1 == 0)
{
NO;
return;
}
}
YES;
}
C. New Rating
你好,Codeforcescode!
凯文曾经是 Codeforces 的参与者。最近,KDOI 团队开发了一个名为 Forcescode 的新在线裁判。
凯文参加过 Forcescode 上的 n n n 场比赛。在第 i i i 场比赛中,他的表现评分为 a i a_i ai。
现在他黑进了 Forcescode 的后台,将选择一个时间间隔 [ l , r ] [l,r] [l,r] ( 1 ≤ l ≤ r ≤ n 1 \leq l \leq r \leq n 1≤l≤r≤n),然后跳过这个时间间隔内的所有比赛。之后,他的评分将按以下方式重新计算:
最初,他的评分为
x
=
0
x=0
x=0;
每次
1
≤
i
≤
n
1 \leq i \leq n
1≤i≤n,在第
i
i
i 场比赛之后:
如果
l
≤
i
≤
r
l \leq i \leq r
l≤i≤r,则跳过这场比赛,等级分保持不变;
否则,他的评级将根据以下规则更新:
如果
a
i
>
x
a_i > x
ai>x,他的评分
x
x
x 将增加
1
1
1;
如果
a
i
=
x
a_i = x
ai=x,他的评分
x
x
x 将保持不变;
如果
a
i
<
x
a_i < x
ai<x,他的评分
x
x
x 将减少
1
1
1。
如果凯文以最佳方式选择了区间
[
l
,
r
]
[l,r]
[l,r],您必须帮助他找到重新计算后的最大可能评分。注意凯文至少要跳过一次比赛。
输入
每个测试包含多个测试用例。输入的第一行包含一个整数
t
t
t (
1
≤
t
≤
5
⋅
1
0
4
1 \leq t \leq 5 \cdot 10^4
1≤t≤5⋅104)—— 测试用例的数量。测试用例说明如下。
每个测试用例的第一行包含一个整数 n n n ( 1 ≤ n ≤ 3 ⋅ 1 0 5 1 \leq n \leq 3 \cdot 10^5 1≤n≤3⋅105)—— 竞赛次数。
第二行包含 n n n 个整数 a 1 , a 2 , … , a n a_1, a_2, \ldots, a_n a1,a2,…,an ( 1 ≤ a i ≤ n 1 \leq a_i \leq n 1≤ai≤n)—— 竞赛中的性能评级。
保证所有测试用例中 n n n 的总和不超过 3 ⋅ 1 0 5 3 \cdot 10^5 3⋅105。
输出
对于每个测试用例,输出一个整数——如果凯文以最佳方式选择间隔,重新计算后可能的最大评级。
示例
输入
5
6
1 2 3 4 5 6
7
1 2 1 1 1 3 4
1
1
9
9 9 8 2 4 4 3 5 3
10
1 2 3 4 1 3 2 1 1 10
输出
5
4
0
4
5
提示
在第一个测试用例中,凯文必须跳过至少一场比赛。如果他选择任何长度为
1
1
1 的区间,他的评分在重新计算后将等于
5
5
5。
在第二个测试用例中,凯文的最佳选择是选择区间 [ 3 , 5 ] [3,5] [3,5]。在重新计算期间,他的评分变化如下:
0 → a 1 = 1 → a 2 = 2 → 跳过 2 → 跳过 2 → 跳过 2 → a 6 = 3 → a 7 = 4 0 \to a_1=1 \to a_2=2 \to \text{跳过}2 \to \text{跳过}2 \to \text{跳过}2 \to a_6=3 \to a_7=4 0→a1=1→a2=2→跳过2→跳过2→跳过2→a6=3→a7=4。
在第三个测试用例中,凯文必须跳过唯一的比赛,因此他的评分将保持在初始值 0 0 0。
在第四个测试用例中,凯文的最佳选择是选择区间 [ 7 , 9 ] [7,9] [7,9]。在重新计算期间,他的评分变化如下:
0 → a 1 = 9 → a 2 = 9 → a 3 = 8 → a 4 = 2 → a 5 = 4 → a 6 = 4 → 跳过 4 → 跳过 4 → 跳过 4 0 \to a_1=9 \to a_2=9 \to a_3=8 \to a_4=2 \to a_5=4 \to a_6=4 \to \text{跳过}4 \to \text{跳过}4 \to \text{跳过}4 0→a1=9→a2=9→a3=8→a4=2→a5=4→a6=4→跳过4→跳过4→跳过4。
在第五个测试用例中,凯文的最佳选择是选择区间 [ 5 , 9 ] [5,9] [5,9]。
解题思路
我们可以设计三个 d p dp dp值 f , g , h f,g,h f,g,h, f [ i ] f[i] f[i]代表第 i i i次比赛不进行任何跳过的评分, g [ i ] g[i] g[i]代表第 i i i次比赛正在进行跳过的最大评分, h [ i ] h[i] h[i]代表第 i i i次比赛已经进行跳过后的最大平方,我们令 c a l c ( a [ i ] ) calc(a[i]) calc(a[i])代表评级的更新规则,则可以得到以下状态转移方程: f [ i ] = f [ i − 1 ] + c l a c ( a [ i ] ) , g [ i ] = max ( g [ i − 1 ] , f [ i ] ) , h [ i ] = max ( h [ i − 1 ] + c a l c ( a [ i ] ) , g [ i − 1 ] ) f[i]=f[i-1]+clac(a[i]),g[i]=\max(g[i-1],f[i]),h[i]=\max(h[i-1]+calc(a[i]),g[i-1]) f[i]=f[i−1]+clac(a[i]),g[i]=max(g[i−1],f[i]),h[i]=max(h[i−1]+calc(a[i]),g[i−1])
实现代码
void solve()
{
int n;
cin >> n;
vi a(n + 1);
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
vi f(n + 1, -inf);
vi g(n + 1, -inf);
vi h(n + 1, -inf);
f[0] = g[0] = h[0] = f[1] = h[1] = 0;
for (int i = 1; i <= n; i++)
{
int v = a[i];
if (f[i - 1] < v)
f[i] = f[i - 1] + 1;
else if (f[i - 1] == v)
f[i] = f[i - 1];
else
f[i] = f[i - 1] - 1;
g[i] = max(f[i], g[i - 1]);
if (i == 1)
continue;
if (h[i - 1] < v)
h[i] = h[i - 1] + 1;
else if (h[i - 1] == v)
h[i] = h[i - 1];
else
h[i] = h[i - 1] - 1;
h[i] = max(h[i], g[i - 1]);
}
cout << h[n] << endl;
}
D. Cool Graph
给你一个无向图,其中有 n n n 个顶点和 m m m 条边。
您最多可以执行以下操作 2 ⋅ max ( n , m ) 2 \cdot \max(n,m) 2⋅max(n,m) 次:
选择三个不同的顶点
a
a
a、
b
b
b 和
c
c
c,然后对每条边
(
a
,
b
)
(a,b)
(a,b)、
(
b
,
c
)
(b,c)
(b,c) 和
(
c
,
a
)
(c,a)
(c,a) 执行以下操作:
如果该边不存在,则添加该边。相反,如果存在,则删除。
当且仅当以下条件之一成立时,图才被称为酷图:
该图没有边,或
该图是一棵树。
您必须通过执行上述操作使图形冷却。请注意,您最多可以进行
2
⋅
max
(
n
,
m
)
2 \cdot \max(n,m)
2⋅max(n,m) 次操作。
可以证明,总是存在至少一个解。
输入
每个测试包含多个测试用例。第一行输入包含一个整数
t
t
t (
1
≤
t
≤
1
0
4
1 \leq t \leq 10^4
1≤t≤104)—— 测试用例的数量。测试用例说明如下。
每个测试用例的第一行包含两个整数 n n n 和 m m m ( 3 ≤ n ≤ 1 0 5 3 \leq n \leq 10^5 3≤n≤105, 0 ≤ m ≤ min ( n ( n − 1 ) 2 , 2 ⋅ 1 0 5 ) 0 \leq m \leq \min\left(\frac{n(n-1)}{2}, 2 \cdot 10^5\right) 0≤m≤min(2n(n−1),2⋅105))—— 顶点数和边数。
接着是 m m m 行, i i i 行包含两个整数 u i u_i ui 和 v i v_i vi ( 1 ≤ u i , v i ≤ n 1 \leq u_i, v_i \leq n 1≤ui,vi≤n)—— 第 i i i 条边所连接的两个节点。
保证所有测试用例中 n n n 的总和不超过 1 0 5 10^5 105,所有测试用例中 m m m 的总和不超过 2 ⋅ 1 0 5 2 \cdot 10^5 2⋅105。
保证给定图中不存在自循环或多重边。
输出
对于每个测试用例,在第一行输出一个整数
k
k
k (
0
≤
k
≤
2
⋅
max
(
n
,
m
)
0 \leq k \leq 2 \cdot \max(n,m)
0≤k≤2⋅max(n,m))—— 运算次数。
然后输出 k k k 行,第 i i i 行包含三个不同的整数 a a a、 b b b 和 c c c ( 1 ≤ a , b , c ≤ n 1 \leq a,b,c \leq n 1≤a,b,c≤n)—— 你在第 i i i 次操作中选择的三个整数。
如果有多个解,可以输出其中任意一个。
示例
输入
5
3 0
3 1
1 2
3 2
1 2
2 3
3 3
1 2
2 3
3 1
6 6
1 2
1 6
4 5
3 4
4 6
3 6
输出
0
1
1 2 3
0
1
1 2 3
3
1 3 6
2 4 5
3 4 6
提示
在第一个测试用例中,图已经是酷图,因为没有边。
在第二个测试用例中,执行唯一的操作后,图变成了一棵树,因此是酷图。
在第三个测试用例中,图已经是酷图,因为它是一棵树。
在第四个测试用例中,执行唯一的操作后,图没有边,因此是酷图。
解题思路
看到限制条件为最多可以进行 2 ⋅ max ( n , m ) 2 \cdot \max(n,m) 2⋅max(n,m) 次操作,结合树的边数为 n − 1 n-1 n−1 ,因此我们可以考虑先将图删掉尽可能能多的边,然后再重新构建出一棵树,刚好可以接近用完操作。
顺着这个思路往下想,对于一个度数大于等于 2 2 2 的点 x x x ,我们可以选择对 x x x 和它的两个邻接点 y y y 和 z z z 进行操作,如果 y y y 和 z z z 存在一条边,则一次性删除了三条边,否则,我们删除了两条边并构建了一条边 ( y , z ) (y,z) (y,z) ,至少删除了一条边。因此,我们最多不会超过 m m m 次,就可以将 这个图删的只剩下孤点和孤边。如果没有孤边,说明所有边已被删除,直接满足要求。
考虑对一堆孤点和孤边进行操作来构建一棵树。
我们可以选取一个孤边作为树的基础。
如果遇见孤点,则对树的两个端点 x x x 和 y y y 以及孤点 z z z 进行操作,这样我们相当于在 x x x 和 y y y 之间插入了一个点 z z z 使得边 x → y x\rarr y x→y 变成 x → z → y x \rarr z \rarr y x→z→y ,然后我们把 z z z 赋值给 y y y 即可。
如果遇见孤边,则对孤边的两个端点 u u u 和 v v v 和树的端点 x x x 进行操作,这样我们相当于将 u u u 和 v v v 与端点 x x x 建边,使得边 u → v , x u \rarr v ,x u→v,x 变成 u → x ← v u \rarr x \larr v u→x←v 。
实现代码
void solve()
{
int n, m;
cin >> n >> m;
vector<set<int>> adj(n + 1);
for (int i = 0; i < m; i++)
{
int u, v;
cin >> u >> v;
adj[u].insert(v);
adj[v].insert(u);
}
if (m == 0)
{
cout << "0\n";
return;
}
vector<array<int, 3>> ans;
auto del = [&](int a, int b, int c)
{
adj[a].erase(b);
adj[b].erase(a);
adj[a].erase(c);
adj[c].erase(a);
if (adj[b].find(c) != adj[b].end())
{
adj[b].erase(c);
adj[c].erase(b);
}
else
{
adj[b].insert(c);
adj[c].insert(b);
}
};
for (int i = 1; i <= n; i++)
{
while (adj[i].size() >= 2)
{
int a = i;
int b = *adj[i].begin();
int c = *next(adj[i].begin());
ans.pb({a, b, c});
del(a, b, c);
}
}
int u = 0, v = 0;
for (int i = 1; i <= n; i++)
{
if (adj[i].size() == 1)
{
u = i;
v = *adj[i].begin();
break;
}
}
if (u)
{
vb vis(n + 1);
vis[u] = vis[v] = 1;
for (int i = 1; i <= n; i++)
{
if (vis[i])
continue;
if (adj[i].size() == 1)
{
ans.pb({u, i, *adj[i].begin()});
vis[i] = 1;
vis[*adj[i].begin()] = 1;
}
else
{
ans.pb({u, v, i});
vis[i] = 1;
v = i;
}
}
}
cout << ans.size() << "\n";
for (auto [a, b, c] : ans)
{
cout << a << " " << b << " " << c << "\n";
}
}
E. Common Generator
对于两个整数 x x x 和 y y y ( x , y ≥ 2 x,y \geq 2 x,y≥2),当且仅当 x x x 可以通过执行下面的操作转换为 y y y 时,我们才会说 x x x 是 y y y 的生成器:
选择
x
x
x 的除数
d
d
d (
d
≥
2
d \geq 2
d≥2),然后将
x
x
x 增加
d
d
d。
例如:
3
3
3 是
8
8
8 的生成器,因为我们可以进行以下运算:
3
→
d
=
3
6
→
d
=
2
8
3 \xrightarrow{d=3} 6 \xrightarrow{d=2} 8
3d=36d=28;
4
4
4 是
10
10
10 的生成器,因为我们可以进行以下运算:
4
→
d
=
4
8
→
d
=
2
10
4 \xrightarrow{d=4} 8 \xrightarrow{d=2} 10
4d=48d=210;
5
5
5 不是
6
6
6 的生成器,因为我们无法通过上述操作将
5
5
5 转化为
6
6
6。
现在,凯文给你一个数组 a a a,由 n n n 个成对不同的整数 ( a i ≥ 2 a_i \geq 2 ai≥2) 组成。
你必须找到一个整数 x ≥ 2 x \geq 2 x≥2,使得每个 1 ≤ i ≤ n 1 \leq i \leq n 1≤i≤n 的生成数 x x x 都是 a i a_i ai 的生成数,或者确定这样的整数不存在。
输入
每个测试包含多个测试用例。输入的第一行包含一个整数
t
t
t (
1
≤
t
≤
1
0
4
1 \leq t \leq 10^4
1≤t≤104)—— 测试用例的数量。测试用例说明如下。
每个测试用例的第一行都包含一个整数 n n n ( 1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1≤n≤105)—— 数组 a a a 的长度。
第二行包含 n n n 个整数 a 1 , a 2 , … , a n a_1, a_2, \ldots, a_n a1,a2,…,an ( 2 ≤ a i ≤ 4 ⋅ 1 0 5 2 \leq a_i \leq 4 \cdot 10^5 2≤ai≤4⋅105)—— 数组 a a a 中的元素。可以保证这些元素是成对不同的。
保证所有测试用例中 n n n 的总和不超过 1 0 5 10^5 105。
输出
对于每个测试用例,输出一个整数
x
x
x—— 您找到的整数。如果不存在有效的
x
x
x,则打印
−
1
-1
−1。
如果有多个答案,可以输出任意一个。
示例
输入
4
3
8 9 10
4
2 3 4 5
2
147 154
5
3 6 8 25 100000
输出
2
-1
7
3
提示
在第一个测试用例中,对于
x
=
2
x=2
x=2:
2
2
2 是
8
8
8 的生成器,因为我们可以进行以下运算:
2
→
d
=
2
4
→
d
=
4
8
2 \xrightarrow{d=2} 4 \xrightarrow{d=4} 8
2d=24d=48;
2
2
2 是
9
9
9 的生成器,因为我们可以进行以下运算:
2
→
d
=
2
4
→
d
=
2
6
→
d
=
3
9
2 \xrightarrow{d=2} 4 \xrightarrow{d=2} 6 \xrightarrow{d=3} 9
2d=24d=26d=39;
2
2
2 是
10
10
10 的生成器,因为我们可以进行以下运算:
2
→
d
=
2
4
→
d
=
2
6
→
d
=
4
10
2 \xrightarrow{d=2} 4 \xrightarrow{d=2} 6 \xrightarrow{d=4} 10
2d=24d=26d=410。
在第二个测试用例中,可以证明不可能找到四个整数的公共生成器。
解题思路
由于一个数 x x x 可以增加它的因数 d d d ,我们很容易就可以想到,要构造一个数 a i a_i ai ,我们只要要构造出它的因数的倍数 k d kd kd 并使得 k d ≤ a i kd \le a_i kd≤ai,我们就可以很轻松的构造出 a i a_i ai 。由于每次操作都是对当前值进行加法,因此我们因数 d d d 选的越小,我们的操作空间越大,所以为了构造出 a i a_i ai ,我们一定是选择它的最小质因数 p p p 来进行构造。
- 考虑 a i a_i ai 为偶数,我们只要选择 x = 2 x=2 x=2,就一定可以构造出 a i a_i ai 。
- 考虑 a i a_i ai 为非质数的奇数,我们也只要选择 x = 2 x=2 x=2,就一定可以构造出 a i a_i ai 。对于 a i a_i ai,我们只需要将 x x x 加上 p − 1 p-1 p−1 次 2 2 2 就可以获得因数最小包含 a i a_i ai 最小质数因子 p p p 的数 2 × p 2\times p 2×p 。由于 a i a_i ai 为非质数的奇数,其最小质因子最小为 3 3 3 ,因此 $a_i \gt 2\times p $,我们只要对 2 × p 2 \times p 2×p 不断加上 p p p 就可以得到 a i a_i ai 。
- 考虑 a i a_i ai 为质数,我们只能选择它自己来获得它。
综上
-
如果 a a a 中质数数量大于 1 1 1 ,则不存在符合要求的整数。
-
如果 a a a 中质数数量等于 0 0 0 ,则选择 x = 2 x=2 x=2 一定可以构造出所有 a i a_i ai 。
-
如果 a a a 中质数数量等于 1 1 1 ,则我们只能选择选择 x = pri x=\text{pri} x=pri , pri \text{pri} pri 为唯一的质数。
考虑检查 x = pri x=\text{pri} x=pri 是否可以生成所有 a i a_i ai
- 对于 a i a_i ai 为偶数,只要 a i ≤ 2 × pri a_i \le 2\times \text{pri} ai≤2×pri ,我们就可以使得选择器包含因数 2 2 2 从而构造出所有偶数。
- 对于 a i a_i ai 为奇数,只要 a i − p ≤ 2 × pri a_i - p \le 2\times \text{pri} ai−p≤2×pri ,我们就可以使得选择器包含因数 2 2 2 从而构造出 2 × p 2 \times p 2×p 进而构造出 a i a_i ai 。
实现代码
const int N = 4e5;
vector<int> minp; // 存储每个数的最小质因子
vector<int> primes; // 存储找到的所有质数
// 欧拉筛函数
void sieve(int n)
{
minp.assign(n + 1, 0); // 初始化最小质因子数组
primes.clear(); // 清空质数数组
for (int i = 2; i <= n; i++)
{
if (minp[i] == 0)
{ // 如果 minp[i] 仍为 0,说明 i 是质数
minp[i] = i; // 记录 i 的最小质因子为自身
primes.push_back(i); // 将 i 添加到质数列表中
}
// 遍历已找到的质数
for (auto p : primes)
{
if (i * p > n)
{ // 如果 i * p 超过 n,停止
break;
}
minp[i * p] = p; // 记录 i * p 的最小质因子
if (p == minp[i])
{ // 如果当前质数等于 i 的最小质因子,停止
break;
}
}
}
}
void solve()
{
int n;
cin >> n;
vi a(n);
int cntPri = 0;
int pri = 0;
for (int i = 0; i < n; i++)
{
cin >> a[i];
if (minp[a[i]] == a[i])
{
cntPri++;
pri = a[i];
}
}
if (cntPri >= 2)
{
cout << "-1\n";
return;
}
if (cntPri == 0)
{
cout << "2\n";
return;
}
for (int i = 0; i < n; i++)
{
if (a[i] == pri)
continue;
int v = a[i] & 1 ? a[i] - minp[a[i]] : a[i];
if (pri * 2 > v)
{
cout << "-1\n";
return;
}
}
cout << pri << "\n";
}
这场没打,第二天就是区赛,不敢打。
感觉这场挺简单的。