AtCoder Beginner Contest 330
B - Minimize Abs 1
难度: ⭐⭐
题目大意
给定n个数Ai和一个范围l, r; 问是否存在一个长度为n的序列Xi, Xi的范围是l ~ r; 对于1 ~ n中的每个i, 都要求满足|Xi - Ai| <= |Y - Ai|, Y是l ~ r中的任意一个数;
解题思路
题目有点难理解, 但是其实就是对于对于每个Ai, 在l ~ r中找到一个与Ai差值最小的数; 很明显, 如果Ai在l ~ r之间, 那么Xi就是Ai, 否则就是l或者r;
神秘代码
#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
const int N = 2e5 + 10, mod = 998244353;
int n, m, res;
signed main() {
int l, r;
cin >> n >> l >> r;
for(int i = 1; i <= n; i++) {
int x;
cin >> x;
if(x <= l) cout << l << ' ';
else if(x >= r) cout << r << ' ';
else cout << x << ' ';
}
return 0;
}
C - Minimize Abs 2
难度: ⭐⭐
题目大意
给定一个数字D, 找到两个非负整数x和y, 使得|x2 + y2 - D|尽可能小; 输出可能的最小值;
解题思路
比较明显的一个二份, 因为D的数据范围是1e12, 所以x和y的范围最大是1e6, 所以我们可以遍历x, 再用二分来找y;
神秘代码
#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
const int N = 2e5 + 10, mod = 998244353;
int n, m, res = 1e13;
signed main() {
cin >> n;
int i = -1;
do{
i++;
int x = i * i;
int l = 0, r = 1e6;
while(l < r){
int mid = l + r + 1>> 1;
int y = mid * mid;
if(x + y <= n) l = mid;
else r = mid - 1;
}
int a = x + l * l;
int b = x + (l + 1) * (l + 1);
res = min(res, min(abs(a - n), abs(b - n)));
}while(i * i <= n);
cout << res;
return 0;
}
D - Counting Ls
难度: ⭐⭐
题目大意
给定一个由o和x组成的字符矩阵; 问有多少种坐标三元组满足以下条件{(x1, y1), (x2, y2), (x3, y3)};
1 - 这三个坐标各不相同, 并且对应的字符都是'o';
2 - 恰好有两个坐标在同一列
3 - 恰好有两个坐标在同一行;
解题思路
根据题意我们发现这三个坐标其实就是一个是直角顶点, 另外两个在两条直角边上; 所以我们只要遍历所有'o'让其作为直角顶点, 设其坐标为(x, y); 我们需要统计第x行' o '的数量a和第y列' o '的数量b, 让他们两两组合, 共有(a - 1) * (b - 1)种(因为要去掉直角顶点); 因为直角顶点不同, 所以这样找出来的所有组合都不会重复; 行和列中' o '的数量可以预处理;
神秘代码
#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
const int N = 2e3 + 10, mod = 998244353;
int n, m, res;
char g[N][N];
int r[N], c[N];
signed main() {
cin >> n;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
cin >> g[i][j];
if(g[i][j] == 'o'){
r[i]++;
c[j]++;
}
}
}
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
if(g[i][j] == 'o'){
res += (r[i] - 1) * (c[j] - 1);
}
}
}
cout << res;
return 0;
}
E - Mex and Update
难度: ⭐⭐⭐
题目大意
给定一个长度为n的数列Ai, 进行q次修改操作(i, x), 将下标为i的数Ai改为x; 每次修改完输出不在这个数列中的最小的非负整数mex;
解题思路
这个题我的第一想法就是用set来维护所有不在数列中的数, 每次输出集合里的第一个数就行; 但是Ai的范围是1e9就让我打消了这个念头; 不过可以注意到n的范围只有2e5; 也就是说, 如果存在值大于2e5的Ai, 那么mex就一定小于等于2e5, 所以我们只维护2e5 + 10中不在数列中的非负整数即可; set的插入查找删除都是O(logn)所以不会超时;
神秘代码
#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
const int N = 2e5 + 10, mod = 998244353;
int n, m, res;
int p[N], f[N];
map<int, int> mp;
set<int> s;
int check(){
for(int i = 1; i <= n; i++) f[i] = p[i];
sort(f + 1, f + 1 + n);
int x = 0;
for(int i = 1; i <= n; i++){
if(f[i] == x) x++;
else break;
}
return x;
}
signed main() {
IOS;
cin >> n >> m;
for(int i = 1; i <= n; i++) {
cin >> p[i];
mp[p[i]]++;
}
for(int i = 0; i <= N; i++){
if(!mp[i]) s.insert(i);
}
while(m--) {
int a, b;
cin >> a >> b;
b = min(b, N);
mp[p[a]]--;
if(!mp[p[a]]) s.insert(p[a]);
if(!mp[b]) s.erase(s.find(b));
mp[b]++;
p[a] = b;
cout << *s.begin() << endl;
}
return 0;
}
F - Minimize Bounding Square
难度: ⭐⭐⭐⭐
题目大意
在一个二维平面上给定n个坐标点和一个移动次数k; 问是否存在一个尽可能的小的正方形, 把它放在一个合适的位置(正方形的四个顶点的坐标都是整数), 我们让这个正方形外面的点移动的到正方形的边上或者内部, 点只能有上下左右四种移动方式, 每次移动的距离为1, 请问能否在k次内把正方形外的所有点都移到正方形的边上或者内部; 如果可以则输出正方形的最小边长;
一个坐标上可以存在多个点, 所以最后可能所有点都汇聚在一个坐标上, 这种情况的最小边长就是0;
解题思路
对于这种有多个不定变量的题, 我们可以二分其中的一个不定量, 然后用它去确定其他不定量; 这里我们可以二分正方形的边长u; 而统计移动次数时, 就是正方形外每个点的横坐标和离它最近的那条竖边的距离加上纵坐标和离它最近的那条横边的距离, 对此我们发现可以独立地看每个点的横纵坐标, 所以我们把横纵坐标都分别都从小到大排序;
现在的问题是如何确定正方形的位置, 根据上面说的, 我们可以把正方形分为两条竖边和两条横边来讨论, 因为方法是一样的, 所以我只说一下如何确定两条竖边的位置:
因为无法直接确定两条竖边的位置, 所以我们可以用现有的横坐标来把其逼近到合法的位置; 设正方形两条竖边的位置为L和R, 从小到大排好序的横坐标为x1, x2 .. xn; 首先我们先取x1和xn, 设len = xn - x1; 如果len小于等于正方形的边长u, 那么我们就把移动L和R那x1和xn包含在内; 如果len > u, 那么就不移动L和R, 那么x1和xn两者的移动次数就是len - u; 然后再判断x2和xn-1; 这样遍历时len呈现不递增的趋势, 所以当我们上一回len <= u时移动L和R将xa和xb包含进去后, 那么对于xa+1和xb-1也一定在L和R之间, 所以这个时候之间退出循环即可; 注意L和R我们不需要具体的数值, 我们只需要只能当前的状态是怎样的即可; 同样的操作遍历两条横边后, 我们就得到了总移动数量, 拿去和k进行比较即可;
神秘代码
#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
const int N = 2e5 + 10, mod = 998244353;
int n, m;
int x[N], y[N];
bool check(int u){
int sum = 0;
for(int i = 1, j = n; i < j; i++, j--){
int len = x[j] - x[i];
if(len <= u) break;
sum += len - u;
}
for(int i = 1, j = n; i < j; i++, j--){
int len = y[j] - y[i];
if(len <= u) break;
sum += len - u;
}
if(sum <= m) return true;
else return false;
}
signed main() {
cin >> n >> m;
for(int i = 1; i <= n; i++){
cin >> x[i] >> y[i];
}
sort(x + 1, x + 1 + n);
sort(y + 1, y + 1 + n);
int l = 0, r = 1e9;
while(l < r){
int mid = l + r >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
cout << l << endl;
return 0;
}