DfC 专项训练题解1
A. Burglar and Matches - 900
题目大意
一个窃贼要去偷火柴,最多偷 \(n\) 盒,店里有 \(m\) 种火柴,每种火柴有 \(a_i\) 盒, 每盒有 \(b_i\) 根火柴,问最多能偷走多少根火柴,
解题思路
贪心签到题,按照每盒的火柴数排序,优先偷火柴多的盒即可。
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <unordered_set>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 25;
const int MOD = 1e9 + 7;
pair<int, int> p[N];
void solve() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= m; ++i) {
cin >> p[i].second >> p[i].first;
}
sort(p + 1, p + 1 + m, greater<>());
LL res = 0;
for (int i = 1; i <= m; ++i) {
if (n <= 0) break;
if (p[i].second > n) {
res += p[i].first * n;
break;
} else {
res += p[i].first * p[i].second;
n -= p[i].second;
}
}
cout << res << endl;
}
signed main() {
ios;
int T = 1;
// cin >> T;
while (T--) {
solve();
}
}
B. Sale - 900
题目大意
Bob去买电视,但是有些电视的价格为负数,Bob最多拿得动 \(m\) 台电视,问Bob在这个过程中最多能挣多少钱。
解题思路
还是贪心签到题,对所有电视价格排序,如果电视价格转正或者达到了数量上限就终止遍历。
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <unordered_set>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 110;
const int MOD = 1e9 + 7;
int a[N];
void solve() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
sort(a + 1, a + 1 + n);
LL res = 0;
for (int i = 1; i <= m; ++i) {
if (a[i] >= 0) {
break;
}
res -= a[i];
}
cout << res << endl;
}
signed main() {
ios;
int T = 1;
// cin >> T;
while (T--) {
solve();
}
}
C. Naughty Stone Piles - 1900
题目大意
有 \(n\) 堆石子,每堆石子有 \(a_1, a_2,\dots, a_n\) 个,我们需要将这些石子合并为一堆,同时每次操作移动的石子个数成为这次操作的代价,而对任意一堆石子,被加入石子的次数不可超过 \(k\) 次。将会询问 \(q\) 次,要输出每次询问的代价最小值。
解题思路
本题给出 \(q\) 次询问,如果不预先处理出结果的话很容易在角量上TLE。仔细思考一下合并的过程,虽然是玩石子但是和nim游戏没有半毛钱关系。如果没有 \(k\) 的限制,答案就十分明显,因为最终会合并到一堆,所以最多的那一堆的石子一定是最后合并的,而最少的一定是最先合并的。由于一堆石子最多被合并 \(k\) 次,我们可以类比为一颗每个节点最多有 \(k\) 支的 \(k\) 叉树,我们需要做的实际上是把这棵树进行合并同时满足向上合并后代价最小,可以类比到我们进行哈夫曼编码时使用的哈夫曼树。所以这题实际上是让我们建立一个权值 \(k\) 叉树再由前缀和贪心求解。
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <unordered_set>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
#define int LL
const int N = 1e5 + 10;
const int MOD = 1e9 + 7;
int a[N];
LL s[N], res[N];
void solve() {
int n, q;
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
sort(a + 1, a + n + 1, greater<>());
for (int i = 1; i <= n; ++i) {
s[i] = s[i - 1] + a[i];
}
for (int i = 1; i <= n; ++i) {
int len = i;
for (int j = 1, l = 2, r; l <= n; len *= i, l = r + 1, ++j) {
r = min(l + len - 1, n);
res[i] += (s[r] - s[l - 1]) * j;
}
}
cin >> q;
while (q--) {
int x;
cin >> x;
x = min(x, n);
cout << res[x] << ' ';
}
}
signed main() {
ios;
int T = 1;
// cin >> T;
while (T--) {
solve();
}
}
D. Permutations - 1500
题目大意
给定两个 \(1\sim n\) 的排列,同时定义一种操作:将原排序的末尾元素移动到任意位置。问我们如果要将第一个排列通过该操作变成第二个排列,最少要经过多少步。
解题思路
我们来考虑每一次操作的实质是什么。由于插入时选择的位置是最优的,所以每个数至多只会被操作一次,所以操作数的取值范围为 \([0. \ n]\ \cap\ \mathbb{N}\) ,每次操作的最优化确定了我们对一个数操作就必然会使得他最终落入合适的位置。那么我们实际上只需要求有多少数不需要通过这种操作处理。由于第二种排列顺序是由数据给定,我们不妨就给这些数的先后顺序记录下来。再扫描第一种排列,求出满足符合第二种排列顺序的,从第一个元素开始的最长子排列长度,最后输出 \(n - i\) 即为所求的最小次数。
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <unordered_set>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
const int MOD = 1e9 + 7;
int a[N], idx[N];
void solve() {
int n;
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
for (int i = 1; i <= n; ++i) {
int tmp;
cin >> tmp;
idx[tmp] = i;
}
for (int i = 1; i < n; ++i) {
if (idx[a[i]] > idx[a[i + 1]]) {
cout << n - i << endl;
return;
}
}
cout << 0 << endl;
}
signed main() {
ios;
int T = 1;
// cin >> T;
while (T--) {
solve();
}
}
E. Shop - 2800
题目大意
给定 \(k\) 个正整数,\(a_1,\dots,a_k\) ,同时给定 \(n\) 次操作,有三种操作可以使用:
- 将 \(a_i\) 赋值为 \(b\);
- 将 \(a_i\) 加上 \(b\);
- 将 \(a_i\) 乘上 \(b\);
现在我们需要从给定的 \(n\) 个操作中选出 \(m\) 个进行操作,使得 \(\prod_{i = 1}^{k} a_i\) 的值最大。
解题思路
这题学习的蒋老师的思路,%%%!
首先要知道如果我们已经选出了 \(m\) 个操作,那么一定是要先赋值/加再乘,这样才能保证让我们操作的那个数最大,现在考虑如何选择这 \(m\) 个操作。
我们最终求的是乘积,那么这个乘的操作就与我们操作哪个数无关,对一个数赋值为 \(b\),可以视为加上 \(a_i - b\),而最后确定的加法可以视为乘上 \(\frac{b}{a_i}\),这样我们就将三种操作统一起来了。
所以将所有的操作都看成乘法,找出对乘法贡献最大的即可。
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <numeric>
#include <iomanip>
#include <cmath>
#include <stack>
#include <queue>
#include <unordered_set>
#include <map>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
const int MOD = 1e9 + 7;
int a[N], t[N], j[N], b[N];
pair<int, int> as[N];
vector<pair<int, int>> add[N];
vector<pair<long double, int>> mul;
bool cmp(const pair<long double, int>& lhs, const pair<long double, int> &rhs) {
return t[lhs.second] < t[rhs.second];
}
void solve() {
int k, n, m;
cin >> k >> n >> m;
for (int i = 0; i < k; ++i) {
cin >> a[i];
}
for (int i = 0; i < n; ++i) {
cin >> t[i] >> j[i] >> b[i];
j[i]--;
switch (t[i]) {
case 1:
as[j[i]] = max(as[j[i]], {b[i], i});
break;
case 2:
add[j[i]].emplace_back(b[i], i);
break;
case 3:
mul.emplace_back(b[i], i);
}
}
for (int i = 0; i < k; ++i) {
if (as[i].first > a[i]) {
add[i].emplace_back(as[i].first - a[i], as[i].second);
}
}
for (int i = 0; i < k; ++i) {
sort(add[i].begin(), add[i].end(), greater<>());
LL v = a[i];
for (auto p : add[i]) {
mul.emplace_back(1.0l * (v + p.first) / v, p.second);
v += p.first;
}
}
sort(mul.begin(), mul.end(), greater<>());
int x = min(m, (int)mul.size());
sort(mul.begin(), mul.begin() + x, cmp);
cout << x << endl;
for (int i = 0; i < x; ++i) {
cout << mul[i].second + 1 << ' ';
}
cout << endl;
}
signed main() {
ios;
int T = 1;
// cin >> T;
while (T--) {
solve();
}
}
F. Fox and Box Accumulation - 1400
题目大意
有 \(n\) 个箱子,每个箱子上最多垒放 \(a_i\) 个箱子,直耸的多个箱子放在一起称作一堆,现在想知道最少需要垒放的堆数。
解题思路
对每一个箱子的承重进行排序,这里为了区分每一个箱子,我对每个箱子进行编号。使用set排序我们的pair,pair是由箱子承重和编号所组成的。我们遍历原set的每一个元素,如果这个元素之前没有使用过并且他的承重比当前堆在一起的箱子个数要多的话,我们就让当前的箱子个数增加,同时把这个元素扔到我们的检验set当中,以后判断时,如果元素在我们的检验set中就continue防止重复计数。
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <set>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 110;
const int MOD = 1e9 + 7;
multiset<pair<int, int>> s;
multiset<pair<int, int>> t;
LL res;
void solve() {
int n;
cin >> n;
for (int i = 1; i <= n; ++i) {
int x;
cin >> x;
s.insert({x, i});
}
while (t.size() != n) {
int tmp = 0;
for (const auto & i : s) {
if (t.contains(i)) continue;
if (i.first >= tmp) {
t.insert(i);
tmp++;
}
}
res++;
}
cout << res << endl;
}
signed main() {
ios;
int T = 1;
// cin >> T;
while (T--) {
solve();
}
}
G. Inventory - 1200
题目大意
说了一堆没有用的情景,实际上就是说,给你几个数,数可能有重复,但是你需要修改这些数的值,使得我们最后得到的数列是一个从 \(1 \sim n\) 的排列。
解题思路
模拟式贪心,我们使用类似sg函数的模式,找第一个没有出现在排列集合的元素,遇到重复/不合理元素的时候,就执行替换操作即可。
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <set>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
const int MOD = 1e9 + 7;
int a[N], res[N];
bool vis[N];
void solve() {
int n, cnt = 1;
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
if (!vis[a[i]] && a[i] <= n) {
res[i] = a[i];
}
vis[a[i]] = true;
}
for (int i = 1; i <= n; ++i) {
if (!res[i]) {
while (vis[cnt]) cnt++;
res[i] = cnt;
vis[cnt] = true;
}
}
for (int i = 1; i <= n; ++i) {
cout << res[i] << ' ';
}
cout << endl;
}
signed main() {
ios;
int T = 1;
// cin >> T;
while (T--) {
solve();
}
}
H. Mike and Fun - 1400
题目大意
给你一个 \(n\times m\) 的\(01\)矩阵,进行 \(q\) 次操作,每次操作可以使得某一格元素的值取非。现在问我们每次操作完毕之后,每行连续 \(1\) 的个数最大为多少。
解题思路
看数据范围,图最大就 \(2.5\times 10^5\) 个数据,操作最多 \(5000\) 次,那么运算量最多在 \(1.25\times 10^9\) 次,codeforces的评测机 \(2\text{s}\) 可以跑约 \(2\times 10^9\) 个数据,所以暴力模拟肯定是可以过的。
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 510;
const int MOD = 1e9 + 7;
int g[N][N];
void solve() {
int n, m, q;
cin >> n >> m >> q;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
cin >> g[i][j];
}
}
while (q--) {
int x, y;
cin >> x >> y;
g[x][y] = !g[x][y];
int max_ = 0;
for (int i = 1; i <= n; ++i) {
int tr = 0;
for (int j = 1; j <= m; ++j) {
if (!g[i][j]) {
tr = 0;
} else {
tr++;
}
max_ = max(max_, tr);
}
}
cout << max_ << endl;
}
}
signed main() {
ios;
int T = 1;
// cin >> T;
while (T--) {
solve();
}
}
I. Unfair Poll - 1700
题目大意
文学课老师按照座位顺序点名,先是第一排从第 \(1\) 个同学到第 \(m\) 个同学,随后是第二排,最后一直到第 \(n\) 排,在往前依次循环,现在想知道被点到名最多的同学以及被点到名最少的同学以及某个特定同学分别被点到名的次数。
解题思路
首先数据范围相当小所以 \(O(n^2)\) 肯定是能过的,其次本题表现出一种规整的规律性,所以在模拟必然TLE的情况下,不妨将一部分加法转化为乘法。初高中的数学题有过什么三角形正方形找规律求第几行第几列是多少的,这个也差不多。那我们就把座位表上每个座位赋上底值,也就是上一次完整的全图遍历时每个学生回答问题的次数。再单独处理剩下最后一轮的点名即可。这样我们的复杂度可以限定在 \(O(n^2)\) 避免了TLE问题。
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 110;
const int MOD = 1e9 + 7;
LL g[N][N];
void solve() {
LL n, m, k, x, y;
cin >> n >> m >> k >> x >> y;
if (n < 3) {
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
g[i][j] = k / (n * m);
}
}
k %= n * m;
for (int i = 1; i <= n; ++i) {
if (k <= 0) break;
for (int j = 1; j <= m; ++j) {
if (k <= 0) break;
g[i][j]++;
k--;
}
}
} else {
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
if (i == 1 || i == n) {
g[i][j] = k / (((n << 1) - 2) * m);
} else {
g[i][j] = k / (((n << 1) - 2) * m) << 1;
}
}
}
k %= ((n << 1) - 2) * m;
for (int i = 1; i <= n; ++i) {
if (k <= 0) break;
for (int j = 1; j <= m; ++j) {
if (k <= 0) break;
g[i][j]++;
k--;
}
}
for (int i = n - 1; i >= 1; --i) {
if (k <= 0) break;
for (int j = 1; j <= m; ++j) {
if (k <= 0) break;
g[i][j]++;
k--;
}
}
}
LL max_ = 0, min_ = 0x3f3f3f3f3f3f3f3f;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
// cout << g[i][j] << ' ';
max_ = max(max_, g[i][j]);
min_ = min(min_, g[i][j]);
}
// cout << endl;
}
cout << max_ << ' ' << min_ << ' ' << g[x][y];
}
signed main() {
ios;
int T = 1;
// cin >> T;
while (T--) {
solve();
}
}
J. One-Way Reform - 2200
题目大意
给定一个无向图,给出 \(m\) 条边,现在希望你给这些边加上方向,使得最终得到的有向图中入度等于出度的点最多,输出入度等于出度的点的个数以及每条边的方向。
解题思路
如果一个点的入度要等于出度,那么他的入度与出度之和就一定为偶数,那么可以猜想到,最终入度等于出度的点个数的最大值就是度数和(连边条数)为偶数的点的个数。我们创造一个不存在的虚点,让它和每一个奇度数的点连线,这样所有奇度数的点都转化为了偶度数的点。那么在这种情况下,这个图是一定有欧拉回路的,因为奇度数的点至多只有一个,也就是我们加入的虚点,那么我们从虚点开始dfs找欧拉回路的时候,就一定能使得遍历全图并且每个点的入度和出度相等。我们记录遍历的顺序,最后删去和我们设定的虚点相关的所有边就能得到满足题意的每条边的方向了。
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <iomanip>
#include <stack>
#include <queue>
#include <unordered_set>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
const int MOD = 1e9 + 7;
unordered_set<int> edge[N];
vector<pair<int, int>> res;
// 搜索欧拉回路
void dfs(int x) {
while (!edge[x].empty()) {
int tmp = *edge[x].begin();
edge[x].erase(tmp);
edge[tmp].erase(x);
res.push_back({tmp, x});
dfs(tmp);
}
}
void solve() {
res.clear();
int n, m;
LL cnt = 0;
cin >> n >> m;
for (int i = 1; i <= m; ++i) {
int x, y;
cin >> x >> y;
edge[x].insert(y);
edge[y].insert(x);
}
for (int i = 1; i <= n; ++i) {
// 建立虚边
if (edge[i].size() & 1) {
edge[i].insert(0);
edge[0].insert(i);
cnt++;
}
}
for (int i = 1; i <= n; ++i) {
dfs(i);
}
cout << n - cnt << endl;
for (auto i : res) {
if (min(i.first, i.second)) {
cout << i.first << ' ' << i.second << endl;
}
}
}
signed main() {
ios;
int T = 1;
cin >> T;
while (T--) {
solve();
}
}
K. Producing Snow - 1600
题目大意
每天会产生一堆雪,他们的体积是 \(V_i\) ,同时由于天气原因,每天所存在的雪会融化一部分(\(T_i\)),问每天一共会融化多少雪。
解题思路
这里采用贪心和二分两种方法作为题解。
贪心解
模拟式贪心,为了防止TLE,我们使用一个大根堆来存储我们的雪,同时对每天融化的阈值处理前缀和。每天向堆中压入当天产生的雪以及前一位的前缀和,就相当于我们让雪都在一天产生,这样每堆雪的初始状态就是原雪量加上前缀和融化量。随后我们每天对堆进行处理。如果那一堆不够融化就全化掉,否则就使用乘法直接计算融化量降低复杂度。
二分解
和贪心一样,先将所有的雪都当作第一天产生的,同时处理前缀和。但是我们根据前缀和数组二分查找每一堆雪会在哪天消失,以此确定每天有哪些雪堆消失不见,这些单独处理,其他的加上每天融化的额定量即可。
AC Codes
Greedy Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <iomanip>
#include <stack>
#include <queue>
#include <unordered_set>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
const int MOD = 1e9 + 7;
int v[N], t[N];
LL s[N], res;
priority_queue<LL, vector<LL>, greater<>> heap;
void solve() {
int n;
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> v[i];
}
for (int i = 1; i <= n; ++i) {
cin >> t[i];
s[i] = s[i - 1] + t[i];
}
for (int i = 1; i <= n; ++i) {
res = 0;
heap.push(v[i] + s[i - 1]);
while (!heap.empty() && s[i] >= heap.top()) {
res += heap.top() - s[i - 1];
heap.pop();
}
res += heap.size() * t[i];
cout << res << ' ';
}
}
signed main() {
ios;
int T = 1;
// cin >> T;
while (T--) {
solve();
}
}
Binary Search Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <iomanip>
#include <stack>
#include <queue>
#include <unordered_set>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
const int MOD = 1e9 + 7;
struct node {
LL n;
LL total;
}pi[N];
LL v[N], t[N];
LL s[N];
void solve() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> v[i];
}
for (int i = 1; i <= n; i++) {
cin >> t[i];
v[i] += s[i - 1];
s[i] = s[i - 1] + t[i];
}
for (int i = 1; i <= n; i++) {
if (v[i] > 0) {
LL a = lower_bound(s, s + n + 1, v[i]) - s;
if (s[a] >= v[i]) {
if (pi[a].n > 0) {
pi[a].n++;
pi[a].total += s[a] - v[i];
} else {
pi[a].n = 1;
pi[a].total = s[a] - v[i];
}
}
} else {
pi[i].n++;
pi[i].total += t[i];
}
}
LL cnt = 0;
for (int i = 1; i <= n; i++) {
cout << t[i] * (i - cnt) - pi[i].total << ' ';
cnt += pi[i].n;
}
cout << endl;
}
signed main() {
ios;
int T = 1;
// cin >> T;
while (T--) {
solve();
}
}
L. Strange Game On Matrix - 1600
题目大意
给定一个 \(n\times m\) 的矩阵,底值 \(k\) ,从每一列中找最上面的 \(1\) ,随后对其下 \(len=min(k,n-i+1)\) 个(即最多 \(k\) 个,不可超过边界)数求和,现在我们想让这个和最大,同时允许将任意的 \(1\) 更改为 \(0\) ,求出最大的总分以及修改的次数。
解题思路
我们既然要求区间的和,先维护一个前缀和数组,对每一列分别计算,最大的区间和就是 \(s_r - s_{l - 1}\) ,为了取到这一列,需要去掉 \(1\) 的数量实际上就是 \(s_{l - 1}\),贪心可解。
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <iomanip>
#include <stack>
#include <queue>
#include <unordered_set>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 110;
const int MOD = 1e9 + 7;
int g[N][N], s[N];
void solve() {
int res = 0, cntR = 0;
int n, m, k;
cin >> n >> m >> k;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
cin >> g[i][j];
}
}
for (int i = 1; i <= m; ++i) {
int sum = 0, cnt = 0;
for (int j = 1; j <= n; ++j) {
s[j] = s[j - 1] + g[j][i];
}
for (int j = 1; j <= n; ++j) {
int tmp = s[j + k - 1] - s[j - 1];
if (tmp > sum || (tmp == sum && s[j - 1] <= cnt)) {
sum = tmp;
cnt = s[j - 1];
}
}
res += sum;
cntR += cnt;
}
cout << res << ' ' << cntR << endl;
}
signed main() {
ios;
int T = 1;
// cin >> T;
while (T--) {
solve();
}
}
M. Math Show - 1800
题目大意
有 \(n\) 个任务, 每个任务有 \(k\) 个子任务,解决第 \(i\) 个子任务的时限为 \(t_i\) ,现在有 \(m\) 分钟的时间完成任务,完成一个子任务可以得到 \(1\) 分,完成一个任务的所有子任务可以额外获得一分,问最多能得到多少分。
解题思路
直接枚举我们可以完成的任务数量,随后选择完成耗时更小的任务模拟就行,(所以这题的1800哪来的)
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <iomanip>
#include <stack>
#include <queue>
#include <unordered_set>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 50;
const int MOD = 1e9 + 7;
int a[N];
void solve() {
int n, k, m;
int res = -1;
int sum = 0;
cin >> n >> k >> m;
for (int i = 1; i <= k; ++i) {
cin >> a[i];
sum += a[i];
}
sort(a + 1, a + 1 + k);
for (int i = 0; i <= n; ++i) {
int tmp = i * (k + 1);
int tim = sum * i, r = n - i;
if (tim > m) break;
for (int j = 1; j <= k; ++j) {
for (int l = 1; l <= r; ++l) {
tim += a[j];
if (tim > m) break;
tmp++;
}
}
res = max(res, tmp);
}
cout << res << endl;
}
signed main() {
ios;
int T = 1;
// cin >> T;
while (T--) {
solve();
}
}
N. Minimizing the String - 1200
题目大意
给定一个字符串,现在允许你删掉其中至多一个字符,问你得到的字典序最小的字符串是什么样的。
解题思路
直接暴力贪,找到第一个比它后面大的字符,给它删掉,得到的一定是最小串。
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <iomanip>
#include <stack>
#include <queue>
#include <unordered_set>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 50;
const int MOD = 1e9 + 7;
int a[N];
void solve() {
int n;
string s;
cin >> n >> s;
for (int i = 0; i < n; ++i) {
if (s[i] > s[i + 1]) {
s.erase(i, 1);
break;
}
}
cout << s << endl;
}
signed main() {
ios;
int T = 1;
// cin >> T;
while (T--) {
solve();
}
}
O. Heist - 800
题目大意
给你几个数,让你找出最大的和最小的之间缺多少个。
解题思路
直接排序求差分,暴力找就行,好像也可以看个数算。
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <iomanip>
#include <stack>
#include <queue>
#include <unordered_set>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 50;
const int MOD = 1e9 + 7;
int a[N];
void solve() {
int n;
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
sort(a + 1, a + n + 1);
LL res = 0;
for (int i = 2; i <= n; ++i) {
res += a[i] - a[i - 1] - 1;
}
cout << res << endl;
}
signed main() {
ios;
int T = 1;
// cin >> T;
while (T--) {
solve();
}
}
P. Born This Way - 1600
题目大意
有一个人要从A地乘坐飞机前往C地,但是必须经由B地中转,现在我们最多可以取消掉 \(k\) 次航班,如果能使得这个人到不了C地的话就输出 \(-1\) ,否则输出这个人最晚到达的时刻。
解题思路
首先考虑一定飞不到的情况,就是我们能够把从A到B或者从B到C的飞机全部取消了,那么就一定飞不到。对于剩下的情况,我们可以考虑找最接近A到B的飞机到的时间从B出发的航班,将这个航班取消,时间就会延迟,如果那之后没有航班就输出 \(-1\) 。
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <iomanip>
#include <stack>
#include <queue>
#include <unordered_set>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
const int MOD = 1e9 + 7;
int a[N], b[N];
void solve() {
int n, m, ta, tb, k;
cin >> n >> m >> ta >> tb >> k;
for (int i = 0; i < n; ++i) {
cin >> a[i];
}
for (int i = 0; i < m; ++i) {
cin >> b[i];
}
if (k >= min(n, m)) {
cout << -1 << endl;
return;
}
int res = 0;
for (int i = 0; i <= k; ++i) {
int j = lower_bound(b, b + m, a[i] + ta) - b;
if (k - i>= m - j) {
cout << -1 << endl;
return;
}
res = max(res, b[j + k - i] + tb);
}
cout << res << endl;
}
signed main() {
ios;
int T = 1;
// cin >> T;
while (T--) {
solve();
}
}
Q. Uniqueness - 1500
题目大意
给定一个数组,现在我们能删除这个数组中的一段区间,问要使得最后得到的数组不含有重复元素,最少需要删掉的区间长度。
解题思路
我们先枚举起点, 再从右向左找到第一个让某元素出现 \(2\) 次的位置作为终点, 直到求出最小长度为止。复杂度 \(O(n^2 \log n)\) 可过。
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <iomanip>
#include <stack>
#include <queue>
#include <unordered_set>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
const int MOD = 1e9 + 7;
int a[N];
unordered_map<int, int> mp;
void solve() {
int n;
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
int res = 0x3f3f3f3f;
for (int i = 1; i <= n; ++i) {
mp.clear();
bool flag = true;
for (int j = 1; j < i; ++j) {
mp[a[j]]++;
if (mp[a[j]] > 1) {
flag = false;
break;
}
}
int cur = n + 1;
for (int j = n; j >= i; --j) {
mp[a[j]]++;
if (mp[a[j]] != 1) break;
cur = j;
}
if (flag) {
res = min(res, cur - i);
}
}
cout << res << endl;
}
signed main() {
ios;
int T = 1;
// cin >> T;
while (T--) {
solve();
}
}
R. Two Arrays - 2100
题目大意
给定两个数组,长数组 \(a\) (长度为 \(n\))和短数组 \(b\) (长度为 \(m\) )。现在我们要对 \(a\) 数组进行 \(m - 1\) 次划分,使得划分后每一段的最小值正好是 \(b_i\) 。问这种划分的方案数。
解题思路
本题可以用贪心,dp解。给出两种做法的思路。
贪心解
首先我们考虑怎么计算这个值。我们要划分出 \(m\) 个区间,就需要插入 \(m - 1\) 个隔板,如果每种隔板有 \(s_i\) 种取法,那么根据乘法原理我们最终的结果就是 \(\prod s_i\)。
接下来我们来确定每种隔板的放法。首先我们需要找到 \(b_i\) 在 \(a\) 中出现的位置,这样为了把 \(b_i\) 框在第 \(i\) 个区间中,我们一定要在 \(b_i\) 到 \(b_{i + 1}\) 之间的某处插上隔板。不妨将题目的数组简化,让每个 \(a_i\) 变成 \(\min_{i<j\le n} a_j\) ,这样可以保证我们的结果与原数组造成的结果不会发生变化。
比如我们原数组为:
那么可以处理出新的数组为:
而如果这时的 \(b\) 数组为:
我们可以发现,第二个 \(14\) 往前的 \(4\) 个数一定要包含在第一个区段当中,因为如果在第二个区段当中就会使得下一区段最小值变小。而这两个 \(20\) ,不难发现,第一个 \(20\) 是可以在第一或者第二区段的,因为对两者的最小值都不造成影响。
所以我们可以认为,当有连续存在的 \(b_i\) 时,\(b_{i + 1}\) 的个数就是第 \(i\) 个隔板放置的方法数。
这样就可以轻松解决了。
DP解
由于数组 \(b\) 是单调递增的,所以和前面贪心的思路一样反向遍历最小值,可以找到每段的标志元素,然后使用dp来算出结果。
AC Codes
Greedy Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <iomanip>
#include <stack>
#include <queue>
#include <unordered_set>
#include <map>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
const int MOD = 998244353;
LL a[N], b[N];
LL suf[N], res = 1;
map<LL, LL> mp;
void solve() {
memset(suf, 0x3f, sizeof suf);
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
for (int i = n; i >= 1; --i) {
suf[i] = min(a[i], suf[i + 1]);
mp[suf[i]]++;
}
cin >> b[1];
if (suf[1] != b[1]) {
cout << 0 << endl;
exit(0);
}
for (int i = 2; i <= m; ++i) {
cin >> b[i];
res = (res % MOD * mp[b[i]] % MOD) % MOD;
}
cout << res << endl;
}
signed main() {
ios;
int T = 1;
// cin >> T;
while (T--) {
solve();
}
}
DP Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <iomanip>
#include <stack>
#include <queue>
#include <unordered_set>
#include <map>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
const int MOD = 998244353;
int a[N], b[N];
LL dp[N];
void solve() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
for (int i = 1; i <= m; ++i) {
cin >> b[i];
}
for (int i = n; i > 1; --i) {
a[i - 1] = min(a[i - 1], a[i]);
}
if (a[1] != b[1]) {
cout << 0 << endl;
return;
}
map<int, int> mp;
for (int i = 1; i <= m; ++i) {
mp[b[i]] = i;
}
dp[1] = 1;
for (int i = 1; i <= n; ++i) {
if (!mp.contains(a[i])) continue;
a[i] = mp[a[i]];
if (!a[i]) continue;
dp[a[i]] = (dp[a[i]] + dp[a[i] - 1]) % MOD;
}
cout << dp[m] << endl;
}
signed main() {
ios;
int T = 1;
// cin >> T;
while (T--) {
solve();
}
}
S. Cow and Treats - 2500
题目大意
田里有 \(n\) 个单位的草,每簇草有自己的甜度 \(s_i\) 。 FJ (这不是cf吗哪来的USACO的FJ) 有 \(m\) 头牛,每头牛有自己的喜好 \(f_i\) 和饥饿度 \(h_i\) 。现在FJ要喂一部分牛,要选出两组牛,但这两组牛的并集不必是全集。现在喂牛的方式是:让牛从一边走到另外一边,如果沿途遇到它喜好的甜度的草,就会吃,吃到饱了会在原地睡觉,从而阻挡两侧的牛。如果一头牛吃东西的时候被阻挡或者从一边走到另外一边都没吃饱,它就会不高兴。现在请求出让选出来的所有牛都不会不高兴的最大牛数量,以及这种情况下方案的总数,对 \(10^9 + 7\) 取模。
解题思路
首先我们要了解到一棵草被吃的次数只会在 \(\{0,1,2\}\) 中取值,\(0\) 意味着没有牛吃到,\(2\) 意味着前一头牛在那边吃饱了并且睡着了,另外一边来的牛又吃到了并且吃饱。
每头牛喜好的草是唯一的,那么我们可以按照草的甜度安排牛的入场顺序。
在这 \(n\) 个单位的草内,必然会存在一个点 \(i\),使得从左边进入的牛恰好走到第 \(i\) 个位置,从右边进入的牛最多走到 \(i+1\) 的位置,因此我们可以枚举这个位置 \(i\) ,然后处理每一种情况,求出从左边恰好走到 \(i\) 点的最优方案数,这样就不会有重复。
为了完成这个操作,我们建立两个数组,slm和srm,分别代表从左端点到 \(i\) 点处当前甜度的草的个数,而使用dp数组来表示每一头牛的属性。
对于一个喜好甜度 \(s_i\) 的牛,如果它从左侧入场,那么方案数为 \(\text{dp}[s_i][\text{slm}_{s_i}] - \text{dp}[s_i][\text{slm}_{s_i} - 1]\) ,如果从右侧入场就有 \(\text{dp}[s_i][\text{slm}_{s_i}]\) 种方案。但是在我们的设定中,一定要有一头牛从左边走到睡着,那么如果出现 \(\text{slm}_{s_i} < \text{srm}_{s_i}\) 的情况,需要从右边支援一头牛前往左边。
接下来考虑喜好其他甜味的草的牛,我们可以类似上面分析模拟,但是要保证左右两边不能取同一头牛,根据容斥规则计算方案数即可。
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <iomanip>
#include <stack>
#include <queue>
#include <unordered_set>
#include <map>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 5e3 + 10;
const int MOD = 1e9 + 7;
int dp[N][N], slm[N], srm[N], s[N];
void solve() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
cin >> s[i];
srm[s[i]]++;
}
for (int i = 1; i <= m; ++i) {
int f, h;
cin >> f >> h;
dp[f][h]++;
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
dp[i][j] += dp[i][j - 1];
}
}
LL res1 = 1, res2 = 0;
for (int i = 0; i <= n; ++i) {
if (i) {
slm[s[i]]++;
srm[s[i]]--;
}
LL t1 = 1, t2 = 0;
int sl, sr;
if (i) {
sl = slm[s[i]];
sr = srm[s[i]];
sr = dp[s[i]][sr] - (sr >= sl);
sl = dp[s[i]][sl] - dp[s[i]][sl - 1];
if (!sl) continue;
if (sr) {
t1 = t1 * sl * sr % MOD;
t2 += 2;
} else {
t1 = t1 * sl % MOD;
t2++;
}
}
for (int j = 1; j <= n; ++j) {
if (j == s[i]) continue;
sl = slm[j];
sr = srm[j];
sl = dp[j][sl];
sr = dp[j][sr];
if (!sl && !sr) continue;
if (!sl || !sr) {
t1 = t1 * (sl + sr) % MOD;
t2++;
} else if (sr == 1 && sl == 1) {
t1 = t1 * 2 % MOD;
t2++;
} else {
t1 = t1 * (sl * sr - min(sl, sr)) % MOD;
t2 += 2;
}
}
// cout << "t1:" << t1 << " t2:" << t2 << endl;
// cout << "-------" << endl;
// cout << "res1:" << res1 << ' ' << "res2:" << res2 << endl;
// cout << "-->><<---" << endl;
if (t2 > res2) {
res1 = t1;
res2 = t2;
// cout << "this has been done" << endl;
} else if (t2 == res2) {
res1 = (res1 + t1) % MOD;
// cout << "what is this" << endl;
}
// cout << "res1:" << res1 << ' ' << "res2:" << res2 << endl;
}
cout << res2 << ' ' << (res2 ? res1 : 1) << endl;
}
signed main() {
ios;
int T = 1;
// cin >> T;
while (T--) {
solve();
}
}
T. Deadline - 1100
题目大意
我们被规定在 \(n\) 天内完成某项任务,但是要完成这个任务需要 \(d\) 天,不过我们可以花 \(x\) 天进行优化,优化后的任务需要的时间为 \(\lceil\frac{d}{x+1}\rceil\) ,问能否在 \(n\) 天内完成这项任务。
解题思路
错解
但是能过
我们直接二分 \(x\) 的取值,如果当前的值能满足题述就输出YES
,否则继续二分,二分结束之后输出NO
。
正解
数学推导。
我们总共需要的时间为 \(x + \lceil\frac{d}{x + 1}\rceil\) ,那么由 AM-GM 不等式(均值不等式),可以得到 \(x + 1 + \lceil\frac{d}{x + 1}\rceil - 1 \ge 2\sqrt{d} - 1\) 。所以我们只要拿 \(2\sqrt{d} - 1\) 和 \(n\) 比较就行了。
笨解
暴力出奇迹
我们直接在 \(0\sim10^6\) 范围内暴力枚举 \(x\) 疯狂判断即可。
AC Codes
Wrong Binary Search Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <iomanip>
#include <cmath>
#include <stack>
#include <queue>
#include <unordered_set>
#include <map>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 5e3 + 10;
const int MOD = 1e9 + 7;
int n, d;
bool check(int mid) {
return mid + ceil((double)d / (mid + 1)) <= n;
}
void solve() {
cin >> n >> d;
if (d <= n) {
cout << "YES" << endl;
return;
}
int l = 0, r = n;
while (l < r) {
int mid = (l + r) >> 1;
if (check(mid)) {
cout << "YES" << endl;
return;
} else {
r = mid;
}
}
cout << "NO" << endl;
}
signed main() {
ios;
int T = 1;
cin >> T;
while (T--) {
solve();
}
}
True Math Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <iomanip>
#include <cmath>
#include <stack>
#include <queue>
#include <unordered_set>
#include <map>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 5e3 + 10;
const int MOD = 1e9 + 7;
int n, d;
void solve() {
cin >> n >> d;
cout << (ceil(2 * sqrt(d)) <= n + 1 ? "YES" : "NO") << endl;
}
signed main() {
ios;
int T = 1;
cin >> T;
while (T--) {
solve();
}
}
Stupid Brute Force Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <iomanip>
#include <cmath>
#include <stack>
#include <queue>
#include <unordered_set>
#include <map>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 5e3 + 10;
const int MOD = 1e9 + 7;
int n, d;
void solve() {
cin >> n >> d;
for (int i = 0; i <= 1000000; i++) {
if ((d - 1) / (i + 1) <= (n - 1 - i)) {
cout << "YES" << endl;
return;
}
}
cout << "NO" << endl;
}
signed main() {
ios;
int T = 1;
cin >> T;
while (T--) {
solve();
}
}
U. Very Suspicious - 1700
题目大意
给定无限大正六边形阵列,现在想知道至少要添加多少条线才能得到至少 \(n\) 个三角形。
解题思路
选了这么多题总算来个正儿八经二分答案了,我们对添加的线条在 \(3\) 到 \(n\) 间二分。 由于我们画的线越多得到的三角形一定越多,所以二分的单调性是得到满足的。
通过画图可以发现,线条与三角的对应关系实际上是二阶间差数列,取 \(mid\) 的三分点 \(p\) ,如果 \(p\) 是 \(2\) 的倍数,那么三角个数为 \(\frac{\frac{p}{2}(1 + (1 + 3 \times (\frac{p}{2}-1)))}{2} + \frac{\frac{p}{2}(2 + (2 + 3 \times (\frac{p}{2}-1)))}{2}\) ,否则就是 \(\frac{\lceil\frac{p}{2}\rceil(1 + (1 + 3 \times (\frac{p}{2}-1)))}{2}+\frac{\lfloor\frac{p}{2}\rfloor(2 + (2 + 3 \times (\frac{p}{2}-1)))}{2}\) 可解。
AC Code
Math Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <iomanip>
#include <cmath>
#include <stack>
#include <queue>
#include <unordered_set>
#include <map>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
#define int LL
const int N = 5e3 + 10;
const int MOD = 1e9 + 7;
int n;
bool check(int mid) {
int p = mid / 3;
int lef, rig;
if (p % 2 == 0) {
lef = (1 + 1 + (p / 2 - 1) * 3) * p / 4 + (2 + 2 + (p / 2 - 1) * 3) * p / 4;
rig = ((p / 2) * (p / 2 + 1) / 2 + (p / 2) * (p / 2 - 1) / 2) * 3;
} else {
lef = (1 + 1 + (p / 2) * 3) * (p + 1) / 4 + (2 + 2 + (p / 2 - 1) * 3) * (p / 2) / 2;
rig = (p / 2) * (p / 2 + 1) / 2 * 6;
}
int cnt = lef * 6 + rig * 2;
if (mid % 3 == 1) {
cnt += p / 2 * 4 + (p + 1) / 2 * 2 * 2;
}
if (mid % 3 == 2) {
cnt += p / 2 * 4 + (p + 1) / 2 * 2 * 2;
cnt += (p + 1) / 2 * 4 + (p / 2 * 2 + 1) * 2;
}
return cnt >= n;
}
void solve() {
cin >> n;
if (n < 3) {
cout << 2 << endl;
return;
}
int l = 3, r = n;
while (l < r) {
int mid = (l + r) >> 1;
if (check(mid)) {
r = mid;
} else {
l = mid + 1;
}
}
cout << l << endl;
}
signed main() {
ios;
int T = 1;
cin >> T;
while (T--) {
solve();
}
}
Heap Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
int mmax = 1e9+7;
vector<int>tar;
int t,n;
void init() {
priority_queue<int, vector<int>, greater<> > q;
for (int i = 0; i < 3; i++) q.push(0);
int x = 0;
int sum = 0;
while (x < mmax) {
tar.push_back(x);
int mmin = q.top();
q.pop();
x += 2 * (sum - mmin);
sum++;
mmin++;
q.push(mmin);
}
}
signed main() {
std::istream::sync_with_stdio(false);
init();
cin >> t;
cin.tie(nullptr);
while (t--) {
cin >> n;
cout << lower_bound(tar.begin(), tar.end(), n) - tar.begin() << '\n';
}
return 0;
}
V. Save More Mice - 1000
题目大意
原点处有一只猫,沿着 \(x\) 轴正向有一些鼠鼠,在 \(n\) 处有一个洞口,如果老鼠到了洞口就可以逃脱猫的追杀。现在每一步猫都会向右移动一格,而你可以选择一只鼠鼠让他右移一格,这个过程持续到场上没有鼠鼠为止,问我们能救下最多多少鼠鼠。
解题思路
贪心取值,先尽量让最靠近洞口的鼠鼠进洞,然后模拟这个过程即可。
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <iomanip>
#include <cmath>
#include <stack>
#include <queue>
#include <unordered_set>
#include <map>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 4e5 + 10;
const int MOD = 1e9 + 7;
int a[N];
void solve() {
int n, k;
cin >> n >> k;
int cnt = 0, res = 0;
for (int i = 1; i <= k; ++i) {
cin >> a[i];
}
sort(a + 1, a + k + 1, greater<>());
for (int i = 1; i <= k; ++i) {
if (cnt >= a[i]) break;
cnt += n - a[i];
res++;
}
cout << res << endl;
}
signed main() {
ios;
int T = 1;
cin >> T;
while (T--) {
solve();
}
}
W. Mikasa - 1800
题目大意
给定两个数 \(n\) 和 \(m\),现在我们要求集合 \(\{x|x = n\oplus i,i\in [0,m]\cap\mathbb{N}\}\) 的 \(\text{MEX}\) 。
解题思路
这里的 \(\text{MEX}\) 和博弈论sg函数的 \(\text{MEX}\) 是同一个东西,实际上就是求最小的没有在上述集合中出现的整数。
首先由数学关系我们要知道 \(x\oplus n = k\) 就意味着 \(n\oplus k = x\) ,那么我们可以通过 \(k\) 去检验结果,也就是说,如果我们 \(n\oplus k\) 在 \(0\sim m\) 中,那么就一定在集合当中,反过来,如果不存在,则 \(k\) 就不在集合中。所以我们现在要构造最小的 \(k\)。因为位运算的优越性,我们可以直接从第 \(30\) 位开始暴力枚举每一位,逐一检验并按位赋值给结果即可。
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <iomanip>
#include <cmath>
#include <stack>
#include <queue>
#include <unordered_set>
#include <map>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 4e5 + 10;
const int MOD = 1e9 + 7;
void solve() {
int n, m;
cin >> n >> m;
m++;
int res = 0;
for (int i = 30; i >= 0; --i) {
if (!(n >> i & 1) ^ (m >> i & 1)) continue;
if (n >> i & 1) break;
res |= 1 << i;
}
cout << res << endl;
}
signed main() {
ios;
int T = 1;
cin >> T;
while (T--) {
solve();
}
}
X. Joking (Easy Version) - 2500
题目大意
到底是谁在这里放了一个交互题!!!
给定整数 \(n\)。
交互器有一个隐藏的整数 \(x\),满足 \(1\leq x\leq n\),你需要通过至多 \(\mathbf{82}\) 次下列询问求出这个 \(x\):
- 给定非空整数集合 \(S\),满足 \(S\) 中的元素都是 \([1,n]\) 中的整数。
交互器会告诉你 \(x\in S\) 是否成立。成立会回复YES
,否则会回复NO
。
但是,交互器可以选择说谎话,我们只保证交互器对任意两次连续询问的回复中至少有一次回复是真的。
除此之外,你还可以对 \(x\) 进行至多 \(\mathbf{2}\) 次直接猜测:
- 给定整数 \(y\),满足 \(1\leq y\leq n\)。
交互器会告诉你 \(x=y\) 是否成立。成立会回复:)
,否则会回复:(
。
当你得到:)
作为回复时你的程序就会被视为通过。
交互器不会在对 \(x\) 的直接猜测上说谎。
如果你在某次猜测的前后各进行了一次询问,这两次询问的回复也至少有一个是真的。
交互器是适应性的,即 \(x\) 的值可能会在交互过程中改变。保证交互器的 \(x\) 总是满足上述询问限制。
交互格式
首先,读入一个整数 \(n(1\leq n\leq10^5)\) 表示 \(x\) 的范围。接下来:
- 如果你希望进行询问:
按照? k S[1] S[2] ... S[k]
的格式输出你想询问的集合 \(S\) 的大小 \(k\) 和集合 \(S\) 中的所有元素 \(S_1,S_2,\cdots,S_k\)。
然后读入一个字符串(只会是YES
和NO
中的一个)表示交互器的回复。
你需要保证 \(1\leq k,S_i\leq n\) 且 \(S\) 中的元素互不相同。
你只能进行至多 \(\mathbf{82}\) 次上述询问。 - 如果你希望进行猜测:
按照! y
的格式输出你想猜测的整数 \(y\)。
然后读入一个字符串(只会是:)
和:(
中的一个)表示交互器的回复。
你需要保证 \(1\leq y\leq n\)。
你只能进行至多 \(\mathbf{2}\) 次上述询问。
一旦你得到了:)
作为回答,你需要立刻结束程序。
注意刷新缓冲区。
解题思路
由于它的结果可能为真也可能为假,那么我们就不得不统筹考虑两次询问,所以将询问区间分块为四个没有交集的部分,\(A, B, C, D\) ,先问其中两者的并集,比如 \(A\cup B\),那么如果返回YES
,就再次询问 \(C\) ,这两次询问至少有一个真返回,如果 YES
就排除 \(D\),否则排除\(C\)。如果一开始返回 NO
那就走逆向操作排除 \(A\) 和 \(B\)。这样可以保证我们最后一定可以将查询数据范围转化为原来的 \(\lceil \frac{3}{4}\rceil\) 倍。这样指数衰减可以在 \(82\) 次询问中得到答案。
另外特判掉 \(n = 3\) 的情况:
当\(n = 3\)时,我们假设三个元素分别为\(A, B, C\)。
我们先查询\(a\),再查询一次\(B\)
- 如果两个都返回
Yes
,则可以排除掉\(C\) - 如果有一个返回
Yes
,另外一个为No
,那我们就可以排除掉No
的那个。 - 如果两个都是
No
。我们再查询一次\(B\),再查询一次\(A\)。如果有一个Yes
,则还是上面两种情况之一。否则四个都为No
,则说明中间两个必为真,即我们可以排除掉\(B\)。
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <numeric>
#include <iomanip>
#include <cmath>
#include <stack>
#include <queue>
#include <unordered_set>
#include <map>
//#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
const int MOD = 1e9 + 7;
bool query(const vector<int>& v) {
cout << "? " << v.size() << ' ';
for (auto i : v) {
cout << i << ' ';
}
cout << endl;
string res;
cin >> res;
return res == "YES";
}
bool response(int x) {
cout << "! " << x << endl;
string res;
cin >> res;
return res == ":)";
}
pair<vector<int>, vector<int>> split(vector<int>& v) {
int n = v.size() / 2;
vector<int> a(v.begin(), v.begin() + n), b(v.begin() + n, v.end());
return {a, b};
}
void dfs(vector<int>& v) {
if (v.size() <= 2) {
if (response(v[0])) return;
response(v[1]);
} else if (v.size() == 3) {
bool a = query({v[0]});
bool b = query({v[1]});
bool c = query({v[1]});
bool d = query({v[0]});
if (a && b || c && d) {
v.erase(v.begin() + 1);
} else if (a || b || c || d) {
v.erase(!a || !d ? v.begin() : v.begin() + 1);
} else {
v.erase(v.begin() + 2);
}
dfs(v);
} else {
auto [s1, s2] = split(v);
if (query(s1)) {
swap(s1, s2);
}
auto [s3, s4] = split(s1);
if (!query(s3)) {
swap(s3, s4);
}
v = s2;
v.insert(v.end(), s3.begin(), s3.end());
dfs(v);
}
}
void solve() {
int n;
cin >> n;
vector<int> v(n);
iota(v.begin(), v.end(), 1);
dfs(v);
}
signed main() {
// ios;
int T = 1;
// cin >> T;
while (T--) {
solve();
}
}
Y. Rorororobot - 1700
题目大意
给定一个 \(n\times m\) 的网格,第 \(i\) 列的 \([1, a_i]\) 行不允许通行。
现在有一个机器人,可以使他上下左右四个方向移动,但是每次它会移动 \(k\) 次, 现在我们想知道能否使得这个机器人从起点走到终点并且不出界也不走到不允许通行的格子。
给定 \(q\) 次询问,每次使用不同的起止点以及 \(k\),求能否完成路线。
解题思路
因为我们怎么走都是走 \(k\) 格,那么如果起止点的横纵坐标差值有一个不是 \(k\) 的整数倍就肯定不行。另外因为不能通行的格子都在上面,我们先让机器人往最下面走,然后检测从最下面去到终点的路上有没有不能走的地方,这个就是经典的RMQ问题,搓个线段树就能解决。
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <numeric>
#include <iomanip>
#include <cmath>
#include <stack>
#include <queue>
#include <unordered_set>
#include <map>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
const int MOD = 1e9 + 7;
int a[N];
struct Tree{
int l, r;
int v;
}tree[N << 2];
void pushup(int u) {
tree[u].v = max(tree[u << 1].v, tree[u << 1 | 1].v);
}
void build(int u, int l, int r) {
tree[u].l = l;
tree[u].r = r;
if (l == r) {
tree[u].v = a[r];
return;
}
int mid = (l + r) >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
pushup(u);
}
int query(int u, int l, int r) {
if (tree[u].l >= l && tree[u].r <= r) return tree[u].v;
int mid = (tree[u].l + tree[u].r) >> 1;
int v = 0;
if (l <= mid) {
v = query(u << 1, l, r);
}
if (r > mid) {
v = max(v, query(u << 1 | 1, l, r));
}
return v;
}
int n, m;
void solve() {
int x1, y1, x2, y2, k;
cin >> x1 >> y1 >> x2 >> y2 >> k;
if ((x2 - x1) % k || (y2 - y1) % k) {
cout << "NO" << endl;
return;
}
x1 += (n - x1) / k * k;
int max_ = query(1, min(y1, y2), max(y1, y2));
cout << (max_ < x1 ? "YES" : "NO") << endl;
}
signed main() {
ios;
cin >> n >> m;
for (int i = 1; i <= m; ++i) {
cin >> a[i];
}
build(1, 1, m);
int T = 1;
cin >> T;
while (T--) {
solve();
}
}
Z. Increasing Matrix - 1100
题目大意
给定一个 \(m\times n\) 的矩阵,里面有一些 \(0\) ,问我们能不能通过把这些 \(0\) 换成其他数使得每行每列都递增,如果能就输出和,否则输出 \(-1\) 。
解题思路
直接贪心模拟即可,尾声题组了,简单一些o( ̄▽ ̄)ブ。
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <numeric>
#include <iomanip>
#include <cmath>
#include <stack>
#include <queue>
#include <unordered_set>
#include <map>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 510;
const int MOD = 1e9 + 7;
int a[N][N];
void solve() {
int n, m, sum = 0;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> a[i][j];
}
}
for (int i = n; i >= 1; i--) {
for (int j = m; j >= 1; j--) {
if (a[i][j] == 0) {
if (a[i + 1][j] - a[i - 1][j] > 1 && a[i][j + 1] - a[i][j - 1] > 1) {
a[i][j] = min(a[i + 1][j], a[i][j + 1]) - 1;
}
}
if (a[i][j] <= a[i - 1][j] || a[i][j] <= a[i][j - 1]) {
cout << -1;
return;
}
sum += a[i][j];
}
}
cout << sum;
}
signed main() {
ios;
int T = 1;
// cin >> T;
while (T--) {
solve();
}
}
_A. Almost Equal - 1200
题目大意
输入一个数字 \(n\),现在我们要把从 \(1\) 到 \(2n\) 的某个排列从头到尾接起来,使得这个圆环当中连续的 \(n\) 个数之和之差都不大于 \(1\),求这个构造列。
解题思路
实际上就是说我们要使得连续 \(n\) 个数的取值只有两种可能。那么构造就很简单,把前 \(n\) 个数和后 \(n\) 个数同步考虑,同时是大小循环放置,前面多一点后面就得多一点,这样就能使得和均衡。
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <numeric>
#include <iomanip>
#include <cmath>
#include <stack>
#include <queue>
#include <unordered_set>
#include <map>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
const int MOD = 1e9 + 7;
int a[N];
void solve() {
int n;
int cnt = 0;
cin >> n;
if (!(n & 1)) {
cout << "NO" << endl;
return;
}
cout << "YES" << endl;
for (int i = 1; i <= n; ++i) {
if (i & 1) {
a[i] = ++cnt;
a[n + i] = ++cnt;
} else {
a[n + i] = ++cnt;
a[i] = ++cnt;
}
}
for (int i = 1; i <= n << 1; ++i) {
cout << a[i] << ' ';
}
}
signed main() {
ios;
int T = 1;
// cin >> T;
while (T--) {
solve();
}
}
_B. Phoenix and Beauty - 1400
题目大意
给出一个长度为 \(n\) 的序列 \(a(1\le a_i \le n)\) ,在其中任意位置插入若干 \([1, n]\) 中的数, 使得它的连续 \(k\) 项和都相等。
解题思路
啊啊啊怎么又是构造!
这里需要连续 \(k\) 项和相等,我们不妨联想一下等和数列,等和数列通过中间消元的方式证出 \(a[i] = a[i + 2]\),那么同理我们也能得到 \(a[i] = a[i + k]\)。
这实际上就是说这个数列具有循环节 \(a[1\sim n]\),由此可构造。
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <numeric>
#include <iomanip>
#include <cmath>
#include <stack>
#include <queue>
#include <unordered_set>
#include <map>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
const int MOD = 1e9 + 7;
int a[N], b[N], c[N];
bool vis[N];
void solve() {
int n, k;
cin >> n >> k;
int bnt = 0, cnt = 0;
for (int i = 1; i <= n; i++) {
vis[i] = false;
}
for (int i = 1; i <= n; i++) {
cin >> a[i];
if (!vis[a[i]]) {
vis[a[i]] = true;
cnt++;
bnt++;
b[bnt] = a[i];
}
}
if (cnt > k) {
cout << -1 << endl;
return;
}
if (cnt < k) {
int t = 1;
for (int i = 1; i <= n && cnt < k; i++) {
if (!vis[i]) {
b[++bnt] = i;
cnt++;
}
}
}
int bbnt = 1, nt = 0;
for (int i = 1; i <= n; i++) {
if (a[i] == b[bbnt]) {
++nt;
c[nt] = a[i];
bbnt++;
if (bbnt == k + 1) {
bbnt = 1;
}
} else {
while (a[i] != b[bbnt]) {
c[++nt] = b[bbnt];
bbnt++;
if (bbnt == k + 1) {
bbnt = 1;
}
}
c[++nt] = b[bbnt];
bbnt++;
if (bbnt == k + 1) {
bbnt = 1;
}
}
}
cout << nt << endl;
for (int i = 1; i <= nt; i++) {
cout << c[i] << ' ';
}
cout << endl;
}
signed main() {
ios;
int T = 1;
cin >> T;
while (T--) {
solve();
}
}
_C. Cow and Friend - 1300
题目大意
兔子要从 \((0,0)\) 跳到 \((x,0)\),每次跳的步长 \(a_i\),问跳到终点最少需要多少步。
解题思路
我们可以贪心选择步长集合中最大的步长,最后一跳往边上跳跳,多加一次即可,同时注意终点很近的时候需要特判一些。
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <numeric>
#include <iomanip>
#include <cmath>
#include <stack>
#include <queue>
#include <unordered_set>
#include <map>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
const int MOD = 1e9 + 7;
void solve() {
int n, x, a;
int max_ = 0;
cin >> n >> x;
bool flag = false;
for (int i = 1; i <= n; i++) {
cin >> a;
if (a == x) {
flag = true;
}
max_ = max(max_, a);
}
if (flag == 1) {
cout << 1 << endl;
} else if (max_ > x) {
cout << 2 << endl;
} else {
cout << int(ceil(x / (max_ * 1.0))) << endl;
}
}
signed main() {
ios;
int T = 1;
cin >> T;
while (T--) {
solve();
}
}
_D. Price Maximization - 1500
题目大意
有 \(n\) 个礼物,现在我们要将他们两两分组,每一组的加权价值是 \(\lfloor \frac{a_i + a_j}{k}\rfloor\) ,现在我们要设计一个分组方案使得总价值最大,并求出最大值。
解题思路
首先我们要知道一个定理:
所以有:
这样我们就拆分出了一个定值,对于剩下的部分我们要使得它尽可能的大,注意到只要让 \(a_i\mod k + a_j\mod k \ge k\) ,我们最终答案就会多 \(1\) 。 那么为了让 \(a_i\mod k + a_j\mod k\) 尽可能的大,就需要让最大的 \(a_i\mod k\) 和最小的 \(a_j\mod k\) 组合,否则数值的溢出绝对更大。在这种情况下就可以求得我们的最大价值。
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <numeric>
#include <iomanip>
#include <cmath>
#include <stack>
#include <queue>
#include <unordered_set>
#include <map>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
const int MOD = 1e9 + 7;
LL a[N];
void solve() {
LL n, k, res = 0;
cin >> n >> k;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
res += a[i] / k;
a[i] %= k;
}
sort(a + 1, a + n + 1);
for (int i = n, j = 1; i >= 1; --i) {
while (j < i && a[j] + a[i] < k) j++;
if (j >= i) break;
res++;
j++;
}
cout << res << endl;
}
signed main() {
ios;
int T = 1;
cin >> T;
while (T--) {
solve();
}
}
_EX_. Wizards and Roads - 3000
题目大意
在某些国家里住着巫师。他们喜欢建造城市和道路。
这个国家过去有 $ k $ 个城市,第 $ j $ 个城市 $ ( 1\le j\le k ) $ 位于一个点 $ (x_{j} , y_{j} ) $ 。决定创建另外 $ n-k $ 个城市。第 $ i $ 个城市 \(( k<i\le n )\) 的坐标为 \(( x_{i} , y_{i} )\) :
- $ x_{i}=(a·x_{i-1}+b) mod (10^{9}+9) $
- $ y_{i}=(c·y_{i-1}+d) mod (10^{9}+9) $
其中,$ a,b , c , d $ 都是质数。并且,$ a≠c,b≠d $。
在建造完所有的 $ n $ 个城市之后,巫师们发现了一些惊人的事情。对于任意两个不同的城市 $ i $ 和 $ j $,都有 $ x_{i}≠x_{j} $ 且 $ y_{i}≠y_{j} $。
城市已经建好了,现在是时候建造道路了!决定使用最困难的(当然也是最强大的)咒语来建造道路。使用这个咒语,如果在 $ u , v (y_{u} $ > $ y_{v})$ 两个城镇之间存在一个点 $ w $,并且在 $ w $ 这个角落内还有一个城市 $ s $,该城市不在角落中,且其 $ x $ 坐标严格在 $ w $ 和 $ u $ 之间,同时 $ y_{s}>y_{v} $,则会创建一条道路。
$ p_{2} ( x_{2} , y_{2} ) $ 和 $ p_{1} ( x_{1} , y_{1} ) ( y_{1} < y_{2} ) $ 的角落是一组点 \((x,y)\),其中至少满足以下两个条件之一:
- $ min(x_{1},x_{2})\le x\le max(x_{1},x_{2}) $ 并且 $ y\ge y_{1} $
- $ y_{1}\le y\le y_{2} $ 且 $ (x-x_{2})·(x_{1}-x_{2})\ge 0 $
解题思路
啊我真的看不懂呀,想知道这题怎么做的可以去看这篇题解:笛卡尔树的分裂与合并
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <numeric>
#include <iomanip>
#include <cmath>
#include <stack>
#include <queue>
#include <unordered_set>
#include <map>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
#define lson ch[x][0]
#define rson ch[x][1]
using namespace std;
typedef long long LL;
const int mod=1e9+9;
int n,q,K,_a,_b,_c,_d,a[100100],b[100100];
int ch[100100][2],rt,f[100100][2],g[100100][2],tp,stk[100100];
pair<int,int>p[100100];
void dfs(int x) {
for (int i = 0, y; i < 2; i++)
if (y = ch[x][i]) {
dfs(y);
f[x][1] = max(f[x][1] + max(f[y][0], f[y][1]), f[x][0] + f[y][0] + 1);
f[x][0] = f[x][0] + max(f[y][0], f[y][1]);
}
}
void dfs(int x,int l,int r,int L,int R) {
g[x][0] = 0xc0c0c0c0, g[x][1] = 0;
if (!x || l > R || r < L)return;
if (R < x) {
dfs(lson, l, x - 1, L, R), g[x][0] = g[lson][0], g[x][1] = g[lson][1];
return;
}
if (L > x) {
dfs(rson, x + 1, r, L, R), g[x][0] = g[rson][0], g[x][1] = g[rson][1];
return;
}
if (L <= l && r <= R) {
g[x][0] = f[x][0], g[x][1] = f[x][1];
return;
}
g[x][0] = g[x][1] = 0;
int y;
y = lson;
dfs(y, l, x - 1, L, R);
g[x][1] = max(g[x][1] + max(g[y][0], g[y][1]), g[x][0] + g[y][0] + 1);
g[x][0] = g[x][0] + max(g[y][0], g[y][1]);
y = rson;
dfs(y, x + 1, r, L, R);
g[x][1] = max(g[x][1] + max(g[y][0], g[y][1]), g[x][0] + g[y][0] + 1);
g[x][0] = g[x][0] + max(g[y][0], g[y][1]);
}
int main() {
scanf("%d%d", &n, &K);
for (int i = 1; i <= K; i++)scanf("%d%d", &p[i].first, &p[i].second);
scanf("%d%d%d%d", &_a, &_b, &_c, &_d);
for (int i = K + 1; i <= n; i++)
p[i] = make_pair((1ll * p[i - 1].first * _a + _b) % mod, (1ll * p[i - 1].second * _c + _d) % mod);
sort(p + 1, p + n + 1, [](pair<int, int> x, pair<int, int> y) { return x.second < y.second; });
for (int i = 1; i <= n; i++)p[i].second = i;
sort(p + 1, p + n + 1);
for (int i = 1; i <= n; i++)a[i] = p[i].first, b[i] = p[i].second;
// for(int i=1;i<=n;i++)printf("%d %d\n",a[i],b[i]);
for (int i = 1; i <= n; i++) {
while (tp && b[stk[tp]] < b[i])ch[i][0] = stk[tp--];
if (tp)ch[stk[tp]][1] = i;
stk[++tp] = i;
}
rt = stk[1], dfs(rt);
scanf("%d", &q);
for (int i = 1, l, r; i <= q; i++) {
scanf("%d%d", &l, &r);
l = lower_bound(a + 1, a + n + 1, l) - a;
r = upper_bound(a + 1, a + n + 1, r) - a - 1;
dfs(rt, 1, n, l, r);
printf("%d\n", max(g[rt][0], g[rt][1]));
}
return 0;
}