Squarepoint Challenge (Codeforces Round 1055, Div. 1 + Div. 2) A~E
A - Increase or Smash
模拟。
对于每个不是最大值的来说,都会经历一次先置为零然后再加的操作,重复项只计算一次;而最大值不用置为零,所以最开始会有一次。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve() {
int n;
cin >> n;
vector<int> a(n + 1);
for (int i = 1; i <= n; i += 1) {
cin >> a[i];
}
int Max = *max_element(a.begin() + 1, a.end());
int cnt[101] {}, ans = 1;
for (int i = 1; i <= n; i += 1) {
if (a[i] != Max) {
if (cnt[a[i]] == 0) {
ans += 2;
cnt[a[i]] += 1;
}
}
}
cout << ans << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
B - Catching the Krug
分类讨论。
以下以 \(A\) 代表抓的人,\(B\) 代表被抓的人。因为 \(A\) 可以斜着跑,所以只要 \(B\) 不在和 \(A\) 同 \(x\) 轴或 \(y\) 轴上,\(A\) 都可以一边追的同时通过斜着跑移动到同一轴上,从而使得距离只到四个边界上就能抓住他,而 \(B\) 想要活久一点就只能朝相反方向跑,所以最后答案就是判断 \(B\) 朝与 \(A\) 相反的两个坐标轴跑, \(A\) 到达这两个坐标轴的最大距离。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve() {
int n, k[2], d[2];
cin >> n >> k[0] >> k[1] >> d[0] >> d[1];
int ans = 0;
for(int x = 0; x <= 1; x += 1) {
if(k[x] < d[x]) {
ans = max(ans, d[x]);
} else if(k[x] > d[x]) {
ans = max(ans, n - d[x]);
}
}
cout << ans << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
C - Triple Removal
前缀和/线段树。
手玩可以发现的是,如果存在两个相邻 \(0/1\),一定通过每次消耗 \(1\) 的代价可以将该区间全部消完(假设区间 \(0/1\) 个数为 \(3\) 的倍数),比如 \(1\textcolor{red}{00}1\textcolor{red}00101011\rightarrow \textcolor{red}{11}0\textcolor{red}{1}01011 \rightarrow \textcolor{red}{00}1\textcolor{red}011\rightarrow \textcolor{red}{111}\),只有 \(010101..\) 交替的时候需要多一次 \(1\) 的代价,后面就都是按照相邻的消法即可。
可以用前缀和维护存不存在相邻的 \(0/1\),我是直接上线段树了。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
template<class Info>
struct SegmentTree {
int n;
vector<Info> info;
SegmentTree(): n(0) {}
SegmentTree(int n_, Info v_ = Info()) {
init(n_, v_);
}
template<class T>
SegmentTree(vector<T> init_) {
init(init_);
}
void init(int n_, Info v_ = Info()) {
init(vector(n_, v_));
}
template<class T>
void init(vector<T> init_) {
n = init_.size();
info.assign((4 << __lg(n) + 1) + 10, Info());
function<void(int, int, int)> build = [&](int u, int l, int r) {
if (l == r) {
info[u] = init_[l - 1];
return ;
}
i64 m = (l + r) / 2;
build(2 * u, l, m);
build(2 * u + 1, m + 1, r);
pushup(u);
};
build(1, 1, n);
}
void pushup(int u) {
info[u] = info[2 * u] + info[2 * u + 1];
}
void modify(int u, int l, int r, int x, const Info &v) {
if (l == r) {
info[u] = v;
return;
}
int m = (l + r) / 2;
if (x <= m) {
modify(2 * u, l, m, x, v);
} else {
modify(2 * u + 1, m + 1, r, x, v);
}
pushup(u);
}
void modify(int u, const Info &v) {
modify(1, 1, n, u, v);
}
Info rangeQuery(int u, int l, int r, int x, int y) {
if (l > y || r < x) {
return Info();
}
if (l >= x && r <= y) {
return info[u];
}
int m = (l + r) / 2;
return rangeQuery(2 * u, l, m, x, y) + rangeQuery(2 * u + 1, m + 1, r, x, y);
}
Info rangeQuery(int l, int r) {
return rangeQuery(1, 1, n, l, r);
}
};
struct Info {
int cnt[2] {};
int val[2] {};
bool adj = false;
};
Info operator+(const Info &l, const Info &r) {
Info res;
if(r.cnt[0] + r.cnt[1] == 0) {
return l;
}
if(l.cnt[0] + l.cnt[1] == 0) {
return r;
}
res.cnt[0] = l.cnt[0] + r.cnt[0];
res.cnt[1] = l.cnt[1] + r.cnt[1];
res.val[0] = l.val[0];
res.val[1] = r.val[1];
res.adj |= (l.val[1] == r.val[0] || l.adj || r.adj);
return res;
}
void solve() {
int n, q;
cin >> n >> q;
SegmentTree<Info> seg(n);
for(int i = 1; i <= n; i += 1) {
int x;
cin >> x;
seg.modify(i, {1 - x, x, x, x});
}
while(q --) {
int l, r;
cin >> l >> r;
auto res = seg.rangeQuery(l, r);
if(res.cnt[0] % 3 || res.cnt[1] % 3) {
cout << "-1\n";
continue;
}
int ans = (r - l + 1) / 3;
ans += res.adj != true;
cout << ans << "\n";
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
D. Division Versus Addition
思维,前缀和/线段树。
以下定义三类数,如果 \(a_i=2^k\),定义为 \(A\) 类型;\(a_i=2^k+1\),为 \(B\) 类型;其余为 \(C\) 类型,定义 \(|X|\) 代表 \(X\) 类型的数量。
\(A\) 类型比较好分析,\(+1\) 并不会增加代价,所以对面对 \(A\) 类型下手,跟着操作即可;\(B\) 类型,对面一旦下手,操作必定会增加一次,所以当对面对 \(B\) 类型下手,我也得去对其他 \(B\) 类型下手,尽可能减少对面能下手的数量,由于我是先手,一定尽可能对 \(B\) 先下手;\(C\) 类型,手玩了一下,感觉是一直操作一定会有一次操作后使得 \(a_i = 2^k-1\),这个时候对面操作就会增加一次了。
所以总的代价是 \(ans = \sum_{i=1}^n\lfloor \log_2{a_i}\rfloor + \lfloor\frac{|B|}2\rfloor+|C|\)。
也可以前缀和,我还是直接上线段树了。
关于更具体的上下界分析,参考[1]。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
template<class Info>
struct SegmentTree {
int n;
vector<Info> info;
SegmentTree(): n(0) {}
SegmentTree(int n_, Info v_ = Info()) {
init(n_, v_);
}
template<class T>
SegmentTree(vector<T> init_) {
init(init_);
}
void init(int n_, Info v_ = Info()) {
init(vector(n_, v_));
}
template<class T>
void init(vector<T> init_) {
n = init_.size();
info.assign((4 << __lg(n) + 1) + 10, Info());
function<void(int, int, int)> build = [&](int u, int l, int r) {
if (l == r) {
info[u] = init_[l - 1];
return ;
}
i64 m = (l + r) / 2;
build(2 * u, l, m);
build(2 * u + 1, m + 1, r);
pushup(u);
};
build(1, 1, n);
}
void pushup(int u) {
info[u] = info[2 * u] + info[2 * u + 1];
}
void modify(int u, int l, int r, int x, const Info &v) {
if (l == r) {
info[u] = v;
return;
}
int m = (l + r) / 2;
if (x <= m) {
modify(2 * u, l, m, x, v);
} else {
modify(2 * u + 1, m + 1, r, x, v);
}
pushup(u);
}
void modify(int u, const Info &v) {
modify(1, 1, n, u, v);
}
Info rangeQuery(int u, int l, int r, int x, int y) {
if (l > y || r < x) {
return Info();
}
if (l >= x && r <= y) {
return info[u];
}
int m = (l + r) / 2;
return rangeQuery(2 * u, l, m, x, y) + rangeQuery(2 * u + 1, m + 1, r, x, y);
}
Info rangeQuery(int l, int r) {
return rangeQuery(1, 1, n, l, r);
}
};
struct Info {
int sum = 0, B = 0;
};
Info operator+(const Info &l, const Info &r) {
return {l.sum + r.sum + (l.B + r.B == 2), (l.B + r.B) % 2};
}
void solve() {
int n, q;
cin >> n >> q;
SegmentTree<Info> seg(n + 1);
for(int i = 1; i <= n; i += 1) {
int x;
cin >> x;
int k = 0;
while((1 << k + 1) <= x) {
k += 1;
}
int y = x - 1;
bool B = (y == (y & -y)) && (x & 1);
seg.modify(i, {k + ((x != (x & -x)) && !B), B});
}
while(q --) {
int l, r;
cin >> l >> r;
cout << seg.rangeQuery(l, r).sum << "\n";
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
E. Monotone Subsequence
思维,拓扑。
因为回答给的集合是一个递增的子序列,所以当回答的长度超过 \(n+1\) 时,直接输出即可;否则我们就删掉这些元素,对剩下的继续询问。
如果 \(n\) 次后都不存在一个递增子序列,说明需要找一个递减子序列。可以发现是,假设询问的是 \(1,2,3,4,...,9,10\),而返回的是 \(1,4,6,9\),说明 \(a_1>a_2/a_3,a_4>a_5,a_6>a_7/a_8,a_9>a_{10}\),即每次询问如果没返回长度大于 \(n\) 的序列,至少可以确定两个数的大小关系,\(n\) 次就至少可以确定 \(n+1\) 个数,每次对这些数建立拓扑关系,最后跑个最长路就可以得到一个递减子序列了。
关于更具体的证明,参考[1:1]。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
std::vector<int> qry(n * n + 1);
std::iota(qry.begin(), qry.end(), 0);
auto ask = [](std::vector<int>& v)->std::vector<int> {
std::cout << "? " << v.size() << " ";
for(auto &x : v) {
std::cout << x + 1 << " ";
}
std::cout << std::endl;
int x;
std::cin >> x;
std::vector<int> res(x);
for(int i = 0; i < x; i += 1) {
std::cin >> res[i];
res[i] -= 1;
}
return res;
};
auto answer = [&n](std::vector<int> &v)->void{
std::cout << "! ";
for(int i = 0; i < n + 1; i += 1) {
std::cout << v[i] + 1 << " ";
}
std::cout << std::endl;
};
std::vector<int> du(n * n + 1);
std::vector<std::vector<int>> adj(n * n + 1);
for(int _ = 0; _ < n; _ += 1) {
auto nqry = ask(qry);
if(nqry.size() >= n + 1) {
answer(nqry);
return;
}
std::vector<int> tmp;
for(auto &v : qry) {
auto u = *prev(upper_bound(nqry.begin(), nqry.end(), v));
if(v == u) {
continue;
}
tmp.push_back(v);
adj[u].push_back(v);
du[v] += 1;
}
qry = move(tmp);
}
std::queue<int> q;
std::vector<int> dp(n * n + 1, 1), path(n * n + 1, -1);
for(int i = 0; i < n * n + 1; i += 1) {
if(!du[i]) {
q.push(i);
}
}
while(!q.empty()) {
auto u = q.front();
q.pop();
for(auto &v : adj[u]) {
dp[v] = dp[u] + 1;
path[v] = u;
if(dp[v] == n + 1) {
std::vector<int> res;
while(v != -1) {
res.push_back(v);
v = path[v];
}
std::reverse(res.begin(), res.end());
answer(res);
return;
}
if(!--du[v]) {
q.push(v);
}
}
}
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t;
std::cin >> t;
while (t--) {
solve();
}
return 0;
}