补题报告:Codeforces Round #762 Div.3
A. Square String?
https://codeforces.com/contest/1619/problem/A
Problem
给定一个字符串,问前一半和后一半相等吗?
Solution
字符串长度分奇偶两种情况:长度为奇数,显然前一半和后一半不相等。长度为偶数时,把前一半和后一半字符串上对应位置逐个判断即可。
Code
#include<bits/stdc++.h>
using namespace std;
int main(){
int T_T=1;
cin>>T_T;
while (T_T--){
string s;
cin >> s;
int len = s.length();
if(len%2==1) {
puts("NO");
continue;
}
bool p = true;
for (int i=0; i<len/2; i++){
if(s[i] != s[i+len/2]){
cout << "NO" << '\n';
p = false;
break;
}
}
if(p) puts("YES");
}
return 0;
}
B. Squares and Cubes
Problem
https://codeforces.com/contest/1619/problem/B
将平方数数组和立方数数组合并,然后去重之后形成一个新的数组: [ 1 , 4 , 8 , 9 , . . . ] [1,4,8,9,...] [1,4,8,9,...]。每次询问给你一个 n n n,问 1 ∼ n 1 \sim n 1∼n中有几个新数组的元素。
Solution
根据容斥原理,
a
n
s
=
ans =
ans= 平方数的个数 + 立方数的个数 - 既是平方数又是立方数的个数(六次方的个数)
C
o
d
e
2
Code2
Code2中注意精度损失,要对
n
n
n加上
0.5
0.5
0.5
Code1
#include<bits/stdc++.h>
using namespace std;
int main(){
int T_T=1;
cin>>T_T;
while (T_T--){
long long n;
cin >> n;
long long ans2=0,ans3=0,ans6=0;
for(long long i=1; i*i<=n; i++) ans2++;
for(long long i=1; i*i*i<=n; i++) ans3++;
for(long long i=1; i*i*i*i*i*i<=n; i++) ans6++;
cout << ans2 + ans3 - ans6 << '\n';
}
return 0;
}
Code2
#include<bits/stdc++.h>
using namespace std;
int main()
{
int T_T;
cin >> T_T;
while (T_T--)
{
double n;
cin >> n;
n += 0.5;
int ans2 = pow(n, 1.0/2);
int ans3 = pow(n, 1.0/3);
int ans6 = pow(n, 1.0/6);
cout << ans2 + ans3 - ans6 << '\n';
}
return 0;
}
C. Wrong Addition
Problem
https://codeforces.com/contest/1619/problem/C
对加法做了新的定义,假如给你两个数 a a a和 b b b,他们的加法规则如下:
- 如果其中一个数字比另一个短,则会添加前导零,使数字长度相同。
- 数字从右到左(即从最低有效数字到最高有效数字)进行处理。
- 在第一步中,她将 a a a的最后一位数字与 b b b的最后一位数字相加,并将其总和写入答案中。
- 在下一步中,她对同一位置的每对数字执行相同的操作,并将结果写入答案的左侧。
例如,数字 a = 17236 a=17236 a=17236 和 b = 3465 b=3465 b=3465,相加将得到 s = 1106911 s=1106911 s=1106911:
17236
+
03465
1106911
\begin{array}{r} 17236\\ +03465\\ \hline 1106911 \end{array}
17236+034651106911
给你两个正整数
a
a
a 和
s
s
s 。找到数
b
b
b ,通过如上所述将
a
a
a 和
b
b
b 相加得到
s
s
s 。或者确定不存在这样的
b
b
b 。
其中
0
<
a
<
s
<
1
0
18
0<a<s<10^{18}
0<a<s<1018.
Solution
设 x x x是 a a a的最低有效位, y y y是 s s s的最低有效位。此时分两种情况:
- x < = y x<=y x<=y 时, b b b 的当前位一定是 y − x y-x y−x
-
x
>
y
x>y
x>y 时,
y
y
y 要向s的下一位借位,
b
b
b的当前位为
y
−
x
y-x
y−x . 如果此时
y
<
x
y<x
y<x ,说明
b
b
b 的这一位为负数,不符合题意。并且当
y
>
=
19
y>=19
y>=19 时显然也是不可能发生的(因为不可能有“
10
+
9
10+9
10+9”这一件事发生)
不知道为什么把条件写成 y > = 20 y>=20 y>=20 也能 A C AC AC (大雾
Code
#include<bits/stdc++.h>
using namespace std;
int main(){
int T_T;
cin >> T_T;
while (T_T--){
vector<int> ans;
long long a, s;
cin >> a >> s;
while (s){
int x = a % 10;
int y = s % 10;
if(x<=y){
ans.emplace_back(y-x);
}else{
s /= 10;
y += s % 10 * 10; //向s的下一位借位
if(y < x || y>=19){
break;
}
ans.emplace_back(y-x);
}
a/=10;
s/=10;
}
if(a) {
puts("-1");
continue;
}
while(!ans.back()) ans.pop_back(); //去除前导零
for (int i=ans.size()-1; i>=0; i--) cout << ans[i];
cout << '\n';
}
return 0;
}
D. New Year’s Problem
Problem
https://codeforces.com/contest/1619/problem/D
Vlad有
n
n
n个朋友,家附近有
m
m
m个商店。他想在至多
n
−
1
n-1
n−1个商店给他所有朋友每人买一件礼物,其中在第
j
(
1
<
=
j
<
=
m
)
j(1<=j<=m)
j(1<=j<=m)个商店给第
i
(
1
<
=
i
<
=
n
)
i(1<=i<=n)
i(1<=i<=n) 个朋友买礼物时,第i个朋友会收获到
p
[
i
]
[
j
]
p[i][j]
p[i][j] 点好感度。
Vlad想知道所有朋友的好感度的最小值的最大值是多少。
Solution
要注意到:如果得不到 x x x点好感度,就不可能得到 x + 1 x+1 x+1 点好感度;如果能得到至少 x x x点好感度,就可以得到至少 x − 1 x-1 x−1点好感度。(依据这个想法, 可以对答案进行二分)
Code1:在假设能去 n n n个商店的情况下,求出所能到的最大值 r r r,然后不断缩小 r r r,使 r r r满足存在一个商店里有至少两个人的好感度大于等于 r r r,那么就满足在n-1个商店里给n个人各买一个礼物这个条件了。其中缩小 r r r的过程可以用二分。
Code2:对所有好感度排序,然后从大到小遍历,遍历过程中对对应的人i和商店j标记,直到所有的人被标记过一遍并且存在一个商店j被标记过两次时停止,此时这个好感度就是答案。
Code1
#include<bits/stdc++.h>
using namespace std;
vector<vector<int>> a;
int n, m;
bool check(int x){
vector<bool> p(m); //判断当前商店有没有好感度大于x的人
for (int i=0; i<m; i++){
for (int j=0; j<n; j++){
if(a[j][i] >= x){
if(p[i]) return true; //如果已经存在好感度大于x的人,
// 那么算上这个人就有两个人在同一个商店的好感度大于等于x了
p[i] = true;
}
}
}
return false;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T_T;
cin >> T_T;
while (T_T--){
cin >> m >> n;
a.assign(n, vector<int> (m));
for (int i=0; i<m; i++){
for (int j=0; j<n; j++){
cin >> a[j][i];
}
}
int l=1,r=0x3f3f3f3f;
for (int i=0; i<n; i++)
r = min(r, *max_element(a[i].begin(), a[i].end()));
while (l < r){
int mid = (l + r + 1) >> 1;
if(check(mid)) l = mid;
else r = mid - 1;
}
}
return 0;
}
Code2
#include<bits/stdc++.h>
using namespace std;
vector<vector<int>> a;
int m, n;
struct Node{
int shop, person, joy;
}b[100007];
bool cmp(Node& a, Node& b){ return a.joy > b.joy; }
int main(){
int T_T;
cin >> T_T;
while (T_T--){
cin >> m >> n;
int cnt = 0;
a.assign(n, vector<int> (m));
for (int i=0; i<m; i++){
for (int j=0; j<n; j++){
int x;
cin >> x;
b[++ cnt] = {i, j, x};
}
}
sort(b+1, b+cnt+1, cmp);
vector<bool> st(n); //记录每个人有没有礼物
vector<bool> vis(m); //记录有没有从这个商店购买礼物
int c = 0; //记录多少人有礼物了
bool flag = false; //有没有一个商店给两个人送出礼物
for (int i=1; ; i++){
if(!st[b[i].person]){ //如果已经给这个人准备礼物了,就不用再买了
st[b[i].person] = true;
c++;
}
if(vis[b[i].shop]){
flag = true;
}else{
vis[b[i].shop] = true;
}
if(flag && c>=n) {
cout << b[i].joy << '\n';
break;
}
}
}
return 0;
}
E. MEX and Increments
Problem
https://codeforces.com/contest/1619/problem/E
给你一个含
n
n
n个元素的数组
a
a
a。在每次操作中,你可以选择任何
j
(
1
≤
j
≤
n
)
j(1≤j≤n)
j(1≤j≤n) 并将元素aj的值增加1。(可以重复选择同一个
j
j
j)
对于从
0
0
0到
n
n
n的每个
i
i
i,确定是否可以使数组的
M
E
X
MEX
MEX正好等于
i
i
i。如果可以,则确定执行此操作的最小操作数。
Solution
思路:想要得到
M
E
X
=
x
MEX=x
MEX=x,就要有
1
∼
x
−
1
1 \sim x-1
1∼x−1所有的数,并且将
x
x
x去除掉(将
x
x
x进行加一操作),故
a
n
s
[
x
]
=
x
ans[x] = x
ans[x]=x的个数 + 得到
1
∼
n
−
1
1 \sim n-1
1∼n−1的操作次数
那么我们只需要贪心的模拟一下上面的思路即可。
贪心的思路:如果原数组中没有
x
x
x,为了得到
M
E
X
=
x
+
1
MEX = x+1
MEX=x+1,则需要找到最接近且小于
x
x
x的数才能使操作次数最小。为了实现这一方式,我们将所有小于
x
x
x的数全部入栈,栈顶永远最接近于当前数。
假设当前枚举到
M
E
X
=
x
MEX=x
MEX=x ,说明我们已经知道了得到
1
∼
x
−
2
1 \sim x-2
1∼x−2的最小操作次数,而得到
x
−
1
x-1
x−1 的最小操作次数是
x
−
v
.
b
a
c
k
(
)
x-v.back()
x−v.back()。得到
1
∼
x
−
1
1 \sim x-1
1∼x−1 的最小操作次数 =
1
∼
x
−
2
1 \sim x-2
1∼x−2 的最小操作次数 + 得到x-1的最小操作次数。如果当前栈空,说明元素小于x的个数小于
x
−
1
x-1
x−1个,那么无论怎么操作也不可能得到
1
∼
x
−
1
1 \sim x-1
1∼x−1,即无法得到
M
E
X
=
x
MEX=x
MEX=x,显然也无法得到
M
E
X
=
x
+
1
,
x
+
2...
MEX=x+1, x+2...
MEX=x+1,x+2...
Code
#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int cnt[N];
vector<int> v(N);
int main(){
int T;
cin >> T;
while (T--){
memset(cnt, 0, sizeof cnt);
int n;
cin >> n;
while (!v.empty()) v.pop_back();
for (int i=0; i<n; i++){
int x;
cin >> x;
cnt[x] ++;
}
long long ans = 0;
for (int i=0; i<=n; i++){
if(ans==-1){
cout << -1 << " \n"[i==n];
continue;
}else{
cout << ans + cnt[i] << " \n"[i==n];
}
while(cnt[i]--)
v.push_back(i);
if(v.empty()){
ans = -1;
}else{
ans += i - v.back();
v.pop_back();
}
}
}
return 0;
}
F. Let’s Play the Hat?
Problem
https://codeforces.com/contest/1619/problem/F
有 n n n个人在 m m m张桌子上共进行 k k k轮游戏(每轮游戏后,人的位置可以变化)且要求每张桌子上只能坐 ⌈ n m ⌉ \lceil \frac{n}{m} \rceil ⌈mn⌉或 ⌊ n m ⌋ \lfloor \frac{n}{m} \rfloor ⌊mn⌋人, k k k轮游戏后,第 i i i个人与 ⌊ n m ⌋ \lfloor \frac{n}{m} \rfloor ⌊mn⌋人的参与次数为 b i b_i bi,要求你构造一个时间表,满足数组 b b b中任意两个数的差的绝对值不大于 1 1 1.
Solution
假设n=8,m=3,k=3.
如下图构造就好了。
红线代表第一张桌子,蓝线代表第二张桌子,黄线代表第三张桌子。
发现只需要从上轮三人桌的后一个位置开始排三人桌就可以满足条件了。
代码中用到了上取整与下取整的转换:
⌈
n
m
⌉
=
⌊
n
+
m
−
1
m
⌋
\lceil \frac{n}{m} \rceil = \lfloor \frac{n+m-1}{m} \rfloor
⌈mn⌉=⌊mn+m−1⌋
Code
#include<bits/stdc++.h>
using namespace std;
int main(){
int T_T;
cin >> T_T;
while (T_T--){
int n, m, k;
cin >> n >> m >> k;
vector<int> v(n);
for (int i=0; i<n; i++) v[i] = i + 1;
for (int i=0; i<k; i++){
int idx = 0;
for (int j=0; j<m; j++){
if(j < n%m) {
cout << n/m+1 << ' ';
for (int o=0; o<n/m+1; o++) cout << v[idx++] << ' ';
}else{
cout << n/m << ' ';
for (int o=0; o<n/m; o++) cout << v[idx++] << ' ';
}
cout << '\n';
}
rotate(v.begin(), v.begin() + (n%m) * ((n+m-1)/m), v.end()); //(n+m-1)/m是n/m的上取整
// rotate的目的是将上一轮三人桌的后的所有人提前,减小很多代码量。
}
cout << '\n';
}
return 0;
}
G. Unusual Minesweeper
Problem
https://codeforces.com/contest/1619/problem/G
在一张网格图上有 n n n个炸弹,第 i i i个炸弹会在 t i t_i ti秒之后自动爆炸,炸弹会呈"+"形瞬间引爆其他炸弹,但是只能引爆距离小于等于 k k k的炸弹,假设 ( x , y ) (x,y) (x,y)处炸弹爆炸,则处于 ( x − k , y ) ∼ ( x + k , y ) (x-k,y) \sim (x+k,y) (x−k,y)∼(x+k,y) 和 ( x , y − k ) ∼ ( x , y + k ) (x,y-k) \sim (x,y+k) (x,y−k)∼(x,y+k) 的炸弹会被引爆(这个过程也是发生在一瞬间的)。每秒钟你可以任意引爆一枚炸弹,需要你求出引爆所有炸弹的最短时间。
Solution
用并查集将所有会互相引爆的炸弹维护起来,按每个集合的最小爆炸时间从小到大排序,然后只需要使 a n s ans ans足在 a n s ans ans秒内所有集合的炸弹都会被引爆即可。
Code
#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int p[N], x[N], y[N], t[N];
int n, k;
int find(int x){
return (x==p[x] ? x : p[x] = find(p[x]));
}
void merge(int a, int b){
p[find(b)] = find(a);
return ;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T_T;
cin >> T_T;
while (T_T --){
cin >> n >> k;
vector<int> v(n);
for (int i=0; i<n; i++) p[i] = i;
for (int i=0; i<n; i++){
cin >> x[i] >> y[i] >> t[i];
}
iota(v.begin(), v.end(), 0);
sort(v.begin(), v.end(), [&](int a, int b){
return (x[a] == x[b] ? y[a] < y[b] : x[a] < x[b]);
});
for (int i=0; i<n-1; i++){
if(x[v[i]]==x[v[i+1]] && y[v[i+1]]-y[v[i]]<=k){
merge(v[i], v[i+1]);
}
}
iota(v.begin(), v.end(), 0);
sort(v.begin(), v.end(), [&](int a, int b){
return (y[a] == y[b] ? x[a] < x[b] : y[a] < y[b]);
});
for (int i=0; i<n-1; i++){
if(y[v[i]]==y[v[i+1]] && x[v[i+1]]-x[v[i]]<=k){
merge(v[i], v[i+1]);
}
}
vector<int> boom_time(n, int(1e9));
for (int i=0; i<n; i++){
boom_time[find(i)] = min(boom_time[find(i)], t[i]);
}
vector<int> dsu_timer;
for (int i=0; i<n; i++){
if(i==find(i)) dsu_timer.push_back(boom_time[i]);
}
sort(dsu_timer.begin(), dsu_timer.end());
dsu_timer.pop_back();
int ans = 0;
while (!dsu_timer.empty()){
if(ans < dsu_timer.back()) ans++;
dsu_timer.pop_back();
}
cout << ans << '\n';
}
return 0;
}