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\) 到达这两个坐标轴的最大距离。

image

点击查看代码
#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;
}

  1. https://codeforces.com/blog/entry/146988 ↩︎ ↩︎

posted @ 2025-10-05 22:16  Ke_scholar  阅读(17)  评论(0)    收藏  举报