Codeforces Round #719 (Div. 3)
A - Do Not Be Distracted!
题意
给定一个长度为 n n n的且只包含大写字母的字符串 s s s
问同一个字符是否只连续出现了一段
思路
直接依次遍历字符,利用map去标记已经出现的字符,检查新出现的字符是否被标记过即可。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 10;
char s[MAXN];
map<char, int> mp;
int main() {
int t;
scanf("%d", &t);
while (t--) {
mp.clear();
int n, flag = 0;
scanf("%d", &n);
scanf("%s", s);
int len = strlen(s);
mp[s[0]] = 1;
for (int i = 1; i < len; i++) {
if (mp[s[i]] == 1 && s[i] != s[i - 1]) {
flag = 1;
}
mp[s[i]] = 1;
}
if (flag == 1) {
printf("NO\n");
} else {
printf("YES\n");
}
}
return 0;
}
B - Ordinary Numbers
题意:
定义了一个数字是 o r d i n a r y ordinary ordinary的,当且仅当它每一位上的数字都相同
问从 1 1 1到 n n n有多少个数字是 o r d i n a r y ordinary ordinary的
思路:
知道要满足每一位都是相同的,那么这个每一位的数可以是1-9,直接暴力跑1-9的每一位的情况,如果小于 n n n就跳出即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
ll t;
scanf("%lld", &t);
while (t--) {
ll n, tot = 0;
scanf("%lld", &n);
for (ll i = 1; i <= 9; i++) {
ll x = i;
while (x <= n) {
tot++;
x = x * 10 + i;
}
}
printf("%lld\n", tot);
}
}
C - Not Adjacent Matrix
题意
需要构造一个 n ∗ n n * n n∗n的矩阵,使得相邻的两个数的差值 > 1 > 1 >1,并且1到 n 2 n^2 n2的数字只能在矩阵中出现一次。
思路
只有在 n = 2 n = 2 n=2的时候不存在答案
其余情况,可以先构造一个 1 − n 2 1- n^2 1−n2的矩阵,从右往左依次排列的矩阵。
之后都可以每一个偶数列的数字将其整体上移一格。第一行移到最后一行,即可满足题意。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e3 + 10;
int a[MAXN][MAXN];
int b[MAXN][MAXN];
int main() {
int t;
scanf("%d", &t);
while (t--) {
int n;
scanf("%d", &n);
int k = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
a[i][j] = k;
k++;
}
}
if (n == 1) {
printf("%d\n", 1);
} else if (n == 2) {
printf("-1\n");
} else {
int k;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (i % 2 == 0) {
if (j + 1 > n) {
k = 1;
} else {
k = j + 1;
}
b[j][i] = a[k][i];
} else {
b[j][i] = a[j][i];
}
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
printf("%d ", a[i][j]);
}
printf("\n");
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
printf("%d ", b[i][j]);
}
printf("\n");
}
}
}
return 0;
}
D - Same Differences
题意
给定了一个数列{ $a $ }
询问存在多少个二元组 ( i , j ) (i,j) (i,j),满足 i < j i<j i<j 且 a j − a i = j − i a_{j} - a_i = j-i aj−ai=j−i
思路
a j − a i = j − i a_{j} - a_i = j-i aj−ai=j−i 可以转换为 a j − j = a i − i a_{j} - j = a_i-i aj−j=ai−i
按照顺序依次遍历每个位置的数
利用map存储数字出现的次数,用vector存储出现的不同的数字,之后遍历vector,这个数字出现了 x x x则可以组成 x ∗ ( x − 1 ) / 2 x*(x-1)/2 x∗(x−1)/2个二元组,最后加起来即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MAXN = 2e5 + 10;
ll a[MAXN];
vector<ll> v;
map<ll, ll> mp;
int main() {
ll t;
scanf("%lld", &t);
while (t--) {
v.clear();
mp.clear();
ll n;
scanf("%lld", &n);
for (ll i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
a[i] = a[i] - i;
if (mp[a[i]] == 0) {
v.push_back(a[i]);
}
mp[a[i]]++;
}
ll tot = 0;
for (ll i = 0; i < v.size(); i++) {
tot += ((mp[v[i]] * (mp[v[i]] - 1)) / 2);
}
printf("%lld\n", tot);
}
return 0;
}
E - Arranging The Sheep
题意
给定了一个字符串 s s s,只包含字符 . 和 *字符分别表示为空地和绵羊
每次操作可以将任意一只绵羊往左或者往右移动一格,只要目标位置存在并且为空地。
要求将所有的绵羊排列成一列(即所有绵羊之间都不能有空地)
问最小的操作数。
思路
先通过遍历一遍得到所有的绵羊数,得到最开始的位置右边的绵羊数。
再依次遍历每一位
如果当前位是绵羊,则左边的绵羊数加1,右边的绵羊数减1。
如果当前位是空地,那么左边和右边的绵羊哪边都至少能够整体移动一格,于是选择左边与右边绵羊数较少的绵羊整体向右移动一格。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MAXN = 1e6 + 10;
char s[MAXN];
int main() {
ll t;
scanf("%lld", &t);
while (t--) {
ll n;
scanf("%lld", &n);
scanf("%s", s);
ll l = 0, r = 0;
for (ll i = 0; i < n; i++) {
if (s[i] == '*') {
r++;
}
}
ll ans = 0;
for (ll i = 0; i < n; i++) {
if (s[i] == '*') {
l++;
r--;
} else {
ans += (min(l, r));
}
}
printf("%lld\n", ans);
}
}
F1 - Guess the K-th Zero (Easy version)
题意
现有一个长度为 n < = 2 ∗ 1 0 5 n <= 2*10^5 n<=2∗105的01序列, t = 1 t=1 t=1
每次你可以输出 ? ? ? l l l r r r来查询 [ l , r ] [l,r] [l,r]的区间和,最多查询20次
要求你找到第 k k k个0的下标是多少(从1开始)
思路
直接利用二分维护一个可行区间 [ l , r ] [l,r] [l,r] 初始 l = 1 , r = n l=1,r=n l=1,r=n
每次查询 [ l , m i d ] [l,mid] [l,mid],区间的长度为 m i d − l + 1 mid-l+1 mid−l+1,查询的返回值 s u m sum sum即代表着区间中 1 1 1的个数
则 m i d − l + 1 − s u m mid-l+1-sum mid−l+1−sum 即代表着区间内0的个数
如果 m i d − l + 1 − s u m > = k mid-l+1-sum >=k mid−l+1−sum>=k ,那么表示第 k k k个 0 0 0在区间 [ l , m i d ] [l,mid] [l,mid]区间内,此时递归 [ l , m i d ] [l,mid] [l,mid]
如果 m i d − l + 1 − s u m < k mid-l+1-sum<k mid−l+1−sum<k,那么表示第 k k k个 0 0 0在区间 [ m i d + 1 , r ] [mid+1,r] [mid+1,r]区间内,此时递归 [ m i d + 1 , r ] [mid+1,r] [mid+1,r]
直到 l = = r l==r l==r,输出即可,查询的小于20次的。
#include <bits/stdc++.h>
using namespace std;
void calc(int l, int r, int k) {
if (l == r) {
cout << "! " << l << endl;
return;
}
int m = (l + r) / 2;
cout << "? " << l << " " << m << endl;
int sum;
cin >> sum;
if ((m - l + 1) - sum >= k) {
calc(l, m, k);
} else {
calc(m + 1, r, k - (m - l + 1) + sum);
}
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int n, t, k;
cin >> n >> t >> k;
calc(1, n, k);
}
F2 - Guess the K-th Zero (Hard version)
题意
现有一个长度为 n < = 2 ∗ 1 0 5 n <= 2*10^5 n<=2∗105的01序列, t = m i n ( n , 1 e 4 ) t=min(n,1e4) t=min(n,1e4)
每次你可以输出 ? ? ? l l l r r r来查询 [ l , r ] [l,r] [l,r]的区间和,最多查询20次
要求你找到第 k k k个0的下标是多少(从1开始)
思路
照用上一题的思路,与上题不同的是,我们不能重复访问,我们需要对其已经访问过的区间进行记忆化。
#include <bits/stdc++.h>
using namespace std;
map<pair<int, int>, int> cache;
void dec(int pos, int L, int R) {
cache[{L, R}]--;
if (L != R) {
int M = (L + R) / 2;
if (pos <= M)
dec(pos, L, M);
else
dec(pos, M + 1, R);
}
}
int main() {
int n, cases;
cin >> n >> cases;
for (int i = 0; i < cases; i++) {
int k;
cin >> k;
int L = 0, R = n - 1;
while (L != R) {
int M = (L + R) / 2;
pair<int, int> range = make_pair(L, M);
if (cache.count(range) == 0) {
cout << "? " << range.first + 1 << " " << range.second + 1 << endl;
cin >> cache[range];
cache[range] = range.second - range.first + 1 - cache[range];
}
int value = cache[range];
if (k <= value)
R = M;
else {
k -= value;
L = M + 1;
}
}
cout << "! " << L + 1 << endl;
dec(L, 0, n - 1);
}
}
G - To Go Or Not To Go?
题意
给定一张 n ∗ m n*m n∗m的图,起点是 ( 1 , 1 ) (1,1) (1,1),终点是 ( n , m ) (n ,m ) (n,m)
每个点都有一个值,其中 − 1 -1 −1表示不可以走, 0 0 0 表示可以走,其他值表示可以走并且存在一个花费为 a i + a j a_i+a_j ai+aj的传送器。
行走只能是上下左右四个方向,每走一步的花费为 w w w
使用传送器可以立即传送到任意一个其他的传送器,花费则为两个传送器上的值之和 a i + a j a_i+a_j ai+aj
问从起点到终点的最小花费,不存在则输出 − 1 -1 −1
思路
从起点搜一次到每个可走到的点的最短距离存在 d i s 1 dis1 dis1中
从终点搜一次到每个可走到的点的最短距离存在 d i s 2 dis2 dis2中
由于可以任意传送,所以如果使用传送器的话肯定只使用一次
所以 d i s 1 [ i ] [ j ] + a [ i ] [ j ] dis1[i][j]+a[i][j] dis1[i][j]+a[i][j]就可以表示从起点走到 ( i , j ) (i,j) (i,j)并使用了这个传送器的花费(如果存在传送器)
同理 d i s 2 [ i ] [ j ] + a [ i ] [ j ] dis2[i][j]+a[i][j] dis2[i][j]+a[i][j]就可以表示从 ( i , j ) (i,j) (i,j)走到终点并使用了这个传送器的花费(如果存在传送器)
维护上面两个的最小值,相加后再与不使用任何传送器的情况(即 d i s 1 [ n ] [ m ] dis1[n][m] dis1[n][m]或者 d i s 2 [ 1 ] [ 1 ] dis2[1][1] dis2[1][1])
注意不存在的情况输出 − 1 -1 −1
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MAXN = 3e3 + 10;
const ll INF = 1e18 + 10;
ll mp[MAXN][MAXN];
ll dir[4][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
ll dis1[MAXN][MAXN];
ll dis2[MAXN][MAXN];
ll n, m, w;
bool check(ll x, ll y) {
if (x >= 1 && x <= n && y >= 1 && y <= m) {
return true;
}
return false;
}
void bfs(ll stx, ll sty, ll dis[MAXN][MAXN]) {
for (ll i = 1; i <= n; i++) {
for (ll j = 1; j <= m; j++) {
dis[i][j] = INF;
}
}
dis[stx][sty] = 0;
queue<pair<ll, ll>> q;
q.push(pair<ll, ll>(stx, sty));
while (!q.empty()) {
pair<ll, ll> p = q.front();
q.pop();
ll x = p.first;
ll y = p.second;
for (ll i = 0; i < 4; i++) {
ll px = dir[i][0] + x;
ll py = dir[i][1] + y;
if (check(px, py) && mp[px][py] != -1 && dis[px][py] > dis[x][y] + w) {
dis[px][py] = dis[x][y] + w;
q.push(pair<ll, ll>(px, py));
}
}
}
}
int main() {
scanf("%lld %lld %lld", &n, &m, &w);
for (ll i = 1; i <= n; i++) {
for (ll j = 1; j <= m; j++) {
scanf("%lld", &mp[i][j]);
}
}
bfs(1, 1, dis1);
bfs(n, m, dis2);
ll ans1 = INF;
ll ans2 = INF;
for (ll i = 1; i <= n; i++) {
for (ll j = 1; j <= m; j++) {
if (mp[i][j] <= 0) {
continue;
}
ans1 = min(ans1, dis1[i][j] + mp[i][j]);
ans2 = min(ans2, dis2[i][j] + mp[i][j]);
}
}
ll ans = min(ans1 + ans2, dis1[n][m]);
if (ans >= INF) {
printf("-1\n");
} else {
printf("%lld\n", ans);
}
return 0;
}