Codeforces Round #841 (Div. 2)
文章目录
比赛网址
A. Joey Takes Money
题目大意
给你一个数列 a n = a 1 , a 2 , a 3 , … , a n a_n = {a_1,a_2,a_3,\dots,a_n} an=a1,a2,a3,…,an
每次你可以选择数列中的两个数 a j a_j aj , a k a_k ak , 并自己选择两个数 x x x , y y y
但是要满足条件 a j ⋅ a k = x ⋅ y a_j \cdot a_k = x \cdot y aj⋅ak=x⋅y ,然后你可以用 x x x , y y y 来替换 a j a_j aj , a k a_k ak
以上操作可以进行无数次,你需要使最后数组的和最大
注意输出时要将答案乘上2022
思路/证明
第一直觉是:在给定 x ⋅ y = k x \cdot y = k x⋅y=k 的情况下,只有让一个数等于 1 1 1 才能让 x + y x + y x+y 达到最大
证明如下:
已知 a j ⋅ a k = x ⋅ y a_j \cdot a_k = x \cdot y aj⋅ak=x⋅y , 那么 : y = a j ⋅ a k x 那么: y = \frac{a_j \cdot a_k}{x} 那么:y=xaj⋅ak
所以 x + y = x + a j ⋅ a k x = F ( x ) x + y = x + \frac{a_j \cdot a_k}{x} = F(x) x+y=x+xaj⋅ak=F(x)
a j a_j aj , a k a_k ak 为定值,所以F(x)实际上是一个对勾函数
x ≥ 1 , y ≥ 1 x \ge 1 , y \ge 1 x≥1,y≥1 ,所以 x x x 的范围实际上是 [ 1 , a j ⋅ a k ] [1, a_j \cdot a_k] [1,aj⋅ak]
对勾函数的最小值包含在此区间内,所以我们对两个端点求值,然后判断大小,最大的即为两数相乘的最大值
当 x = 1 x = 1 x=1 时, F ( x ) = a j ⋅ a k F(x) = a_j \cdot a_k F(x)=aj⋅ak , 当 x = a j ⋅ a k x = a_j \cdot a_k x=aj⋅ak 时, F ( x ) = a j ⋅ a k F(x) = a_j \cdot a_k F(x)=aj⋅ak
所以当 x = 1 x = 1 x=1 , y = a j ⋅ a k y = a_j \cdot a_k y=aj⋅ak 时, x + y x+y x+y 达到最大
所以 :在给定 x ⋅ y = k x \cdot y = k x⋅y=k 的情况下,只有让一个数等于 1 1 1 才能让 x + y x + y x+y 达到最大
所以这个数组最后的情况是: a n ′ = 1 , 1 , 1 , … , ∏ i = 1 n a i a_n^\prime = {1,1,1, \dots , \prod_{i = 1}^{n} a_i } an′=1,1,1,…,∏i=1nai
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long int lli;
lli total_ask, total_num;
signed main()
{
ios::sync_with_stdio(false),cin.tie(0), cout.tie(0);
cin >> total_ask;
for(int temp = 0 ; temp < total_ask ; temp++){
lli ans = 1, put;
cin >> total_num;
for(int temp2 = 0 ; temp2 < total_num ; temp2++){
cin >> put;
ans *= put;
}
ans += total_num - 1;
cout << ans*2022 << "\n";
}
return 0;
}
B. Kill Demodogs
题目大意
有一个 长度为 n n n 的矩阵,每个点 ( i , j ) (i , j) (i,j) 的权值为 i ⋅ j i \cdot j i⋅j
需要从 ( 1 , 1 ) (1,1) (1,1) 前往 ( n , n ) (n,n) (n,n) , 途中将路过的权值相加,注意要加上 开头和结尾即: ( 1 , 1 ) (1,1) (1,1) , ( n , n ) (n,n) (n,n)
将结果乘于 2022 并 mod ( 1 0 9 + 7 ) (10^9 + 7) (109+7)
思路/证明
由图可知,虚线左右两边的值是关于虚线对称的
所以我们可以吧下半部分舍去,只参考上半部分
我们观察在同一列时值的情况
在点 ( i , j ) , i ∈ [ 1 , j ] (i,j) , i \in [1,j] (i,j),i∈[1,j] 上,起权值 $ v = i \cdot j$ ,可以观察到, 当 i = j i = j i=j的时候权值达到最大
又因为人物只能向右或向下行走,不能斜线走,所以我们采用之字形走法,这样可以尽可能的接近对角线
所以最大权值和为
V
s
u
m
=
∑
i
=
1
n
i
⋅
i
+
∑
i
=
1
n
−
1
i
⋅
(
i
+
1
)
V_{sum} = \sum_{i = 1}^{n} i\cdot i + \sum_{i = 1}^{n - 1} i \cdot (i + 1)
Vsum=i=1∑ni⋅i+i=1∑n−1i⋅(i+1)
这两个求和实际上可以化简:
∑
i
=
1
n
n
2
的前
n
项和为
S
n
=
n
(
n
+
1
)
(
2
n
+
1
)
6
证明如下
:
(
n
−
1
)
3
−
n
3
=
(
n
3
+
3
n
2
+
3
n
+
1
)
−
n
3
=
3
n
2
+
3
n
+
1
依次枚举
2
3
−
1
3
=
3
×
1
2
+
3
×
1
+
1
3
3
−
2
3
=
3
×
2
2
+
3
×
2
+
1
…
…
…
…
(
n
−
1
)
3
−
n
3
=
3
n
2
+
3
n
+
1
将上述式子相加,得
:
(
n
−
1
)
3
−
1
=
3
×
[
1
2
+
2
2
+
3
2
+
⋯
+
n
2
]
+
3
×
[
1
+
2
+
3
+
⋯
+
n
]
+
n
(
n
−
1
)
3
−
1
=
3
×
S
n
+
3
×
n
(
n
+
1
)
2
+
n
化简得
:
S
n
=
n
(
n
+
1
)
(
2
n
+
1
)
6
所以可以得出
:
∑
i
=
1
n
i
⋅
i
+
∑
i
=
1
n
−
1
i
⋅
(
i
+
1
)
=
S
n
+
S
n
−
1
+
n
(
n
−
1
)
2
化简得
:
∑
i
=
1
n
i
⋅
i
+
∑
i
=
1
n
−
1
i
⋅
(
i
+
1
)
=
n
(
n
+
1
)
(
4
n
−
1
)
6
\begin{align} &\sum_{i = 1}^{n} n^2 的前n项和为 \quad S_n = \frac{n(n+1)(2n+1)}{6}\\ &证明如下:\\ &(n - 1)^3 - n^3 = (n^3+3n^2+3n+1)-n^3=3n^2+3n+1\\ &依次枚举\\ &2^3 - 1^3 = 3 \times 1^2 + 3 \times 1 + 1\\ &3^3 - 2^3 = 3 \times 2^2 + 3 \times 2 + 1\\ &\dots \dots\dots \dots\\ &(n - 1)^3 - n^3 =3n^2+3n+1\\ &将上述式子相加,得:\\ &(n - 1)^3 - 1 = 3 \times [1^2 + 2^2 + 3^2 +\cdots + n^2] + 3\times [1+2+3+\cdots +n]+n\\ &(n - 1)^3 - 1 = 3 \times S_n + 3\times \frac{n(n+1)}{2}+n\\ &化简得:S_n = \frac{n(n+1)(2n+1)}{6}\\ &所以可以得出:\\ &\sum_{i = 1}^{n} i\cdot i + \sum_{i = 1}^{n - 1} i \cdot (i + 1) = S_n + S_{n-1} + \frac{n(n-1)}{2}\\ &化简得:\\ &\sum_{i = 1}^{n} i\cdot i + \sum_{i = 1}^{n - 1} i \cdot (i + 1) = \frac{n(n+1)(4n-1)}{6} \end{align}
i=1∑nn2的前n项和为Sn=6n(n+1)(2n+1)证明如下:(n−1)3−n3=(n3+3n2+3n+1)−n3=3n2+3n+1依次枚举23−13=3×12+3×1+133−23=3×22+3×2+1…………(n−1)3−n3=3n2+3n+1将上述式子相加,得:(n−1)3−1=3×[12+22+32+⋯+n2]+3×[1+2+3+⋯+n]+n(n−1)3−1=3×Sn+3×2n(n+1)+n化简得:Sn=6n(n+1)(2n+1)所以可以得出:i=1∑ni⋅i+i=1∑n−1i⋅(i+1)=Sn+Sn−1+2n(n−1)化简得:i=1∑ni⋅i+i=1∑n−1i⋅(i+1)=6n(n+1)(4n−1)
最后不要忘记乘上2022,我们可以在我们推出得结论上乘上2022,即:
a
n
s
=
n
(
n
+
1
)
(
4
n
−
1
)
6
⋅
2022
=
337
n
(
n
+
1
)
(
4
n
−
1
)
(
m
o
d
1
0
9
+
7
)
ans =\frac{n(n+1)(4n-1)}{6}\cdot2022 = 337n(n+1)(4n-1)\quad (mod\quad10^9+7)
ans=6n(n+1)(4n−1)⋅2022=337n(n+1)(4n−1)(mod109+7)
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long int lli;
lli put, total_ask;
lli mod(lli number){
return number % (1000000007);
}
signed main()
{
cin >> total_ask;
for(lli temp = 0 ; temp < total_ask ; temp++){
cin >> put;
lli ans = mod(337*mod(mod(put*(put+1))*(4*put - 1)));
cout << ans << "\n";
}
return 0;
}
C. Even Subarrays
题目大意
给你一个数列 a n = a 1 , a 2 , … , a n a_n = a_1, a_2, \dots , a_n an=a1,a2,…,an
问有多少个区间的异或和有偶数个除数
思路/证明
首先我们要明确,什么样的数有偶数个除数,或者说什么样的数有奇数个除数
假设我们现在有一个数 a a a ,有一个除数为 i i i ,那么可以知道的是另一个除数为 a i \frac{a}{i} ia
这样我们会发现只要存在一个除数,那么必然有另外一个除数与其相对
那什么时候会只有一个呢 , 即 i = a i i = \frac{a}{i} i=ia , 换句话说,这个数是完全平方数
解决完第一个问题,那我们考虑一下区间异或和以及异或的相关性质
异或是用来判断两个数是否相等,但这不重要,重要的是如果一个数被相同的数异或两个,那么结果是它本身
即: a ∧ b = c c ∧ b ⟺ a ∧ b ∧ b = a a^{\wedge} b = c \quad c^{\wedge} b \Longleftrightarrow a^{\wedge} b^{\wedge} b = a a∧b=cc∧b⟺a∧b∧b=a
接下来就是区间问题了
区间求和一般是用前缀和, 在这里也可以使用前缀和,[a,b]^[a,c] = [b,c]
另外我们可以采取另一种方法来确定有多少种区间和
如果在预处理完前缀和后再一个一个枚举的话会超时
我们是已经可以知道总共的区间和的,即 ( 1 + n ) n 2 \frac{(1+n)n}{2} 2(1+n)n ,因为枚举的次数是等差数列
所以我们可以算出不符合条件的区间个数,然后相减即可
求出来有多少个左边界使得这个区间的异或和为完全平方数就行了
已知 a ∧ b = c a^{\wedge} b = c a∧b=c ,那么 a ∧ c = b a^{\wedge} c = b a∧c=b , c即是平方数,这个平方数我们是可以枚举出来的
另外值得注意的是 :memset()不能随便用,会超时
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long int lli;
const lli N = 3*1e5;
lli arr[N], Find[3*N];//Find用来存储前缀和为i的区间有多少个
lli total_ask, total_num, ans=0;
signed main()
{
ios::sync_with_stdio(false),cin.tie(0), cout.tie(0);
cin >> total_ask;
while(total_ask--){
cin >> total_num;
lli wc = 2*total_num; //wc为异或和所能达到的最大值
for(lli temp = 0 ; temp <= wc ; temp++) Find[temp] = 0;
Find[0]++;
lli ans = 0;
for(int temp = 1 ; temp <= total_num ; temp++){
lli number;
cin >> number;
arr[temp] = arr[temp - 1]^number;
for(int temp2 = 0 ; temp2*temp2 <= wc ; temp2++) //枚举出所有可能出现的平方数
ans += Find[arr[temp]^(temp2*temp2)];
Find[arr[temp]]++;
}
cout << ((1+total_num)*total_num)/2 - ans << "\n";
}
return 0;
}
D. Valiant’s New Map
题目大意
有一个长宽分别为 n n n , m m m 的地图,地图上的每个点 ( i , j ) (i,j) (i,j) 都被一栋高 h h h 的楼占据
存在一个 l × l l \times l l×l 的区间, 区间内的楼高度均大于 l l l
问最大的 l l l 是多少
思路/证明
首先可以想到: 假设边长为 i i i 的时候成立,那么边长小于 i i i 的时候也一定成立,可能存在大于 i i i的情况成立
这可以让我们联想到二分法, 如果当前边长可以,那么就继续二分大于 i i i的部分
接下来就是如何判断是否存在边长为i的区域存在了
我们可以使用二维前缀和,将高度大于等于 i i i 的点值设为1,小于 i i i 的点的值设为0
如果该区域的和等于 边长乘边长,则存在
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 10000;
typedef long long int lli;
lli total_ask, vertical, level, number;
bool Check(lli num, vector<vector<lli>> origin){
vector<vector<int>>div_(vertical + 1, vector<int>(level + 1));
for(int temp = 1 ; temp <= vertical ; temp++){
for(int temp2 = 1 ;temp2 <= level ; temp2++){
lli tem = div_[temp - 1][temp2] + div_[temp][temp2 - 1] - div_[temp-1][temp2-1];
if(origin[temp][temp2] >= num)
div_[temp][temp2] = tem+1;
else
div_[temp][temp2] = tem;
}
}
for(int temp = num ; temp <= vertical ; temp++){
for(int temp2 = num ; temp2 <= level ; temp2++){
lli one = div_[temp - num][temp2];
lli two = div_[temp][temp2 - num];
lli there = div_[temp - num][temp2 - num];
lli Judge = div_[temp][temp2] - one - two + there;
if(Judge == num*num)
return 1;
}
}
return 0;
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> total_ask;
while(total_ask--){
cin >> vertical >> level;
vector<vector<lli>> origin(vertical + 1, vector<lli>(level + 1));
for(int temp = 1 ; temp <= vertical ; temp++){
for(int temp2 = 1 ; temp2 <= level ; temp2++)
cin >> origin[temp][temp2];
}
lli left = 1, right = min(vertical, level) + 1;
while(left < right){
lli mid = (left + right) >> 1;
if(Check(mid, origin)) left = mid+1;
else right = mid;
}
cout << left-1 << "\n";
}
return 0;
}
注:E F 以后再说…
毕竟我也不会(指拉格朗日插值法)