Codeforces Round #807 (Div. 2) A-E
Codeforces Round #807 (Div. 2)
https://codeforces.com/contest/1705
A. Mark the Photographer
排个序,然后把数组砍成两半,看看每个位置上的对应差值是否符合
#include <bits/stdc++.h>
using namespace std;
int a[205];
void solve () {
int n, x;
cin >> n >> x;
for (int i = 0; i < 2*n; i ++)
cin >> a[i];
sort (a, a + 2*n);
for (int i = 0; i < n; i ++) {
if (a[i+n] - a[i] < x) {
cout << "NO\n";
return ;
}
}
cout << "YES\n";
}
int main () {
int t;
cin >> t;
while (t --) {
solve ();
}
}
B. Mark the Dust Sweeper
题意:
[i, j)
[a[i],a[j-1]]全大于0,变:a[i]->a[i]-1; a[j]->a[j]+1
除a[n]外,全变为0
左减右加
分析:
x变为1的代价为x+1
//实质是打通0,堆积到尾部
统计中间的0的个数 + 每个数的代价之和
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 5;
int a[N];
void solve () {
int n, st = -1, ans = 0;
cin >> n;
for (int i = 0; i < n; i ++) {
cin >> a[i];
ans += a[i];
if (st == -1 && a[i] != 0)
st = i;
}
if (st == n-1 || st == -1) {
cout << "0\n";
return ;
}
int cnt = 0;
for (int i = st; i < n - 1; i ++) {
if (a[i] == 0)
cnt ++;
}
ans -= a[n-1];
cout << cnt + ans << endl;
}
signed main () {
int t;
cin >> t;
while (t --) {
solve ();
}
}
//[i, j)
//[a[i],a[j-1]]全大于0,变:a[i]->a[i]-1; a[j]->a[j]+1
//除a[n]外,全变为0
//左减右加
//盲猜
//...x 0...代价为x+1
//......x 0代价为x
//打通到尾部
//统计中间的0的个数
C. Mark and His Unfinished Essay
从最后一段一直往前模拟
递推字符在串中的位置
(想不到啊...)
可以拿样例模拟一下来理解
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 5;
int a[N], n;
char s[N];
// int find (int x, vector<int> v[]) {
// for (int i = 1; i <= n; i ++)
// for (auto j : v[i]) {
// if (x == j)
// return i;
// }
// return -1;
// }
void solve () {
int n, m, q;
//string s;
cin >> n >> m >> q >> s;
//s = ' ' + s;
vector <int> len(m+1), l(m+1), r(m+1);
len[0] = n;
for (int i = 0; i < m; i ++) {
cin >> l[i] >> r[i];
len[i+1] = len[i] + r[i] - l[i] + 1;
}
while (q --) {
int x;
cin >> x;
x --;
for (int i = m - 1; i >= 0; i --) {
if (x >= len[i])
x += l[i] - 1 - len[i];
}
cout << s[x] << endl;
}
}
signed main () {
int t;
cin >> t;
while (t --) {
solve ();
}
}
//从最后一段一直往前模拟
//递推字符在串中的位置
//对于每一个字母记录数字
//在对应的字母后面往后加
//+n*i-前面空出来的
D. Mark and Lightbulbs
题意:
给定串s,t
对于s[i-1]!=s[i+1],可变s[i](0变1,1变0)
求把s变成t需要多少步
分析:
e.g. 01001 可变为 01101,01011,00101
模拟多个样例后可发现,改变的本质是:
增加或减少连续段1的长度,但总连续段数量不变
故连续"1"段数量相同的可以互相转化
最小步数为端点差值
#include <bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int, int> pii;
const int N = 2e5 + 5;
void solve () {
int n;
string s, t;
cin >> n >> s >> t;
if (s[0] != t[0] || s[n-1] != t[n-1]) {
cout << "-1\n";
return ;
}
vector <pii> a, b;
for (int i = 0; i < n; i ++) {
if (s[i] == '0') continue;
int j = i;
while (j + 1 < n && s[j + 1] == '1') j ++;
a.push_back ({i, j});
i = j;
}
for (int i = 0; i < n; i ++) {
if (t[i] == '0') continue;
int j = i;
while (j + 1 < n && t[j + 1] == '1') j ++;
b.push_back ({i, j});
i = j;
}
int m = a.size ();
if (m != b.size ()) {
cout << "-1\n";
return ;
}
int ans = 0;
for (int i = 0; i < m; i ++) {
ans += abs (a[i].first - b[i].first) + abs (a[i].second - b[i].second);
}
cout << ans << endl;
}
signed main () {
int t;
cin >> t;
while (t --) {
solve ();
}
}
//对于s[i-1]!=s[i+1],可变s[i]
//改变此处后,与其相邻的可变性取反
//本质:增加或减少连续段1的长度,但总连续段数量不变
//最小步数,端点差值
E. Mark and Professor Koro
题意:
操作:把两个相同的x替换成x+1,求最后剩下的数
q次修改,每次输出上述数字
分析:
二进制加减法(以左边为最低位)
加:找到右边第一个等于0的,改为1,中间全变为0
减:找到右边第一个等于1的,改为0,中间全变为1
"找": 线段树二分
注意:
加减是从左往右开始找
总查询是从右往左(因为要找最高位)
线段树上二分
原理:
1.判断当前区间是否符合(一般为区间最右端点),否则返回r+1
2.若l=r,返回。
3.查询左区间。
4.若左区间不符合,查询右区间。
过程中可以顺便查询答案和修改标记。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 105, M = 2e5 + 100; //M!!
int a[N], cnt[N], n, m;
struct Node {
int l, r, sum, add;
}st[N<<2];
void pushup (int u) {
st[u].sum = st[u<<1].sum + st[u<<1|1].sum;
}
void update (int u, int add) {
st[u].sum += (st[u].r - st[u].l + 1) * add;
st[u].add += add;
}
void pushdown (int u) {
if (st[u].add) {
update (u << 1, st[u].add);
update (u << 1 | 1, st[u].add);
st[u].add = 0;
}
}
void build (int u, int l, int r) {
if (l == r) {
st[u] = {l, r, cnt[r]};
return ;
}
st[u] = {l, r};
int mid = l + r >> 1;
build (u << 1, l, mid);
build (u << 1 | 1, mid + 1, r);
pushup (u);
}
void modify (int u, int l, int r, int add) {
if (st[u].l >= l && st[u].r <= r) {
update (u, add);
return ;
}
pushdown (u);
int mid = st[u].l + st[u].r >> 1;
if (l <= mid) modify (u << 1, l, r, add);
if (r > mid) modify (u << 1 | 1, l, r, add);
pushup (u);
}
//二分查找:加
int query0 (int u, int l) {
if (st[u].r < l) return -1;
if (st[u].sum == st[u].r - st[u].l + 1) return -1; //全1
if (st[u].l == st[u].r) return st[u].r;
pushdown (u);
int t = query0 (u << 1, l);
if (~t) return t;
return query0 (u << 1 | 1, l);
}
//二分查找:减
int query1 (int u, int l) {
if (st[u].r < l) return -1;
if (st[u].sum == 0) return -1; //全0
if (st[u].l == st[u].r) return st[u].r;
pushdown (u);
int t = query1(u << 1, l);
if (~t) return t;
return query1 (u << 1 | 1, l);
}
int querymax (int u) {
if (st[u].l == st[u].r) return st[u].r;
pushdown(u);
//高位开始查找
if (st[u << 1 | 1].sum) return querymax (u << 1 | 1);
return querymax (u << 1);
}
int main () {
cin >> n >> m;
for (int i = 1; i <= n; i ++) {
cin >> a[i];
cnt[a[i]] ++;
}
//二进制拆分
for (int i = 1; i < M; i ++) {
cnt[i + 1] += cnt[i] / 2;
cnt[i] %= 2;
}
build (1, 1, M);
while (m --) {
int k, l, pos;
cin >> k >> l;
pos = query1 (1, a[k]);
modify (1, pos, pos, -1);
if (pos != a[k]) modify (1, a[k], pos - 1, 1);
a[k] = l;
pos = query0 (1, a[k]);
modify (1, pos, pos, 1);
if (pos != a[k]) modify (1, a[k], pos - 1, -1);
cout << querymax (1) << endl;
}
}
//操作:把两个相同的x替换成x+1,求最后剩下的数
//q次修改,每次输出上述数字
//二进制加减法(以左边为最低位)
//加:找到右边第一个等于0的,改为1,中间全变为0
//减:找到右边第一个等于1的,改为0,中间全变为1
//"找": 线段树二分
//注意:
//加减是从左往右开始找
//总查询是从右往左(因为要找最高位)
线段树的debug真是 _