刷CF #1700
CF2201B
题目描述
有 \(2n\) 张牌,每张牌上写有编号 \(1, 1, 2, 2, \ldots, n, n\)。也就是说,对所有 \(j=1,2,\ldots,n\),恰好有 \(2\) 张编号为 \(j\) 的牌。每张牌的正面只写有一个数字。
你要玩一个翻牌游戏。初始时,全部 \(2n\) 张牌都是牌背朝上(不显示数字的一面)。每回合,你要翻开恰好两张牌。如果这两张牌的数字相同,你就将它们从场上移除。否则,你需要把它们重新扣回原来的位置。你在所有 \(2n\) 张牌都被移除时获胜。注意,你不需要同时翻两张牌,因此你可以先看到第一张牌的数字后,再决定翻哪一张作为第二张。
考虑如下贪心算法来玩这个游戏。起始时,\(2n\) 张牌被按某个顺序一排放好。你每一步的策略如下:
- 如果你曾经翻过的两张牌中存在一对数字相同的牌,那么翻开这两张牌。
- 否则,先去翻第一张你到目前为止还没翻过的牌。假设这张牌上的数字是 \(x\)。
- 然后,如果你曾经翻过另一张数字为 \(x\) 的牌,去翻那一张。
- 否则,再去翻第一张你到目前为止(包括本轮刚翻的)还没翻过的牌作为第二张。
可以证明,上述算法的每一步选择都是唯一确定的。
你需要解决与上述算法相关的如下问题:
- 给定 \(n\) 与 \(k\),请找出一种 \(2n\) 张牌的排列方式,使得按照上述算法恰好需要 \(k\) 步才能获胜。
如果不存在满足条件的排列方式,请输出无解。
\(^{\text{*}}\)这里,“第一张牌”指的是当前序列中符合条件的最靠前的那一张。
题解
偏构造的题。
首先思考最优情况,肯定是 \([1,1,2,2,\cdots,n,n]\),这样只需要 \(n\) 次。最劣情况是 \([1,2,1,3,2,3,2,4,\cdots,n,n−1,n]\),需要 \(2 \times n - 1\) 次。
我们让前 \(t\) 种牌构造最优,而后 \(n−t\) 种构造最劣,于是需要翻牌的次数为 \(2t−1+(n−t)=k\),解得 \(t=k−n+1\),因此我们得到了通解构造:\([1,2,1,…,k−n+1,k−n,k−n+1,k−n+2,k−n+2,…,n,n]\)。
Code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void sol(){
int n, k;
scanf("%d%d",&n,&k);
if (k < n || k >= 2 * n){
printf("No\n");
return;
}
printf("Yes\n1 ");
for (int i = 2; i <= k - n + 1; i++) printf("%d %d ", i, i - 1);
printf("%d ", k - n + 1);
for (int i = k - n + 2; i <= n; i++) printf("%d %d ", i, i);
printf("\n");
}
int main(){
int T;
scanf("%d",&T);
while (T--) sol();
return 0;
}
CF917B
众所周知,Max 是她朋友中最擅长玩电子游戏的人。她的朋友们非常嫉妒她,因此专门为她设计了一个游戏,想证明她并不是最厉害的玩家。这个游戏在一张有向无环图(DAG)上进行,图有 \(n\) 个顶点和 \(m\) 条边。每条边上写着一个小写英文字母。
Max 和 Lucas 进行游戏。Max 先手,然后 Lucas,轮流进行。每位玩家有一个弹珠,初始时分别位于某个顶点。每到自己的回合,玩家必须沿着某条边移动自己的弹珠(若某个顶点 \(v\) 有一条到 \(u\) 的出边,则该玩家可以将弹珠从 \(v\) 移动到 \(u\))。如果玩家从顶点 \(v\) 移动到顶点 \(u\),那么该回合的“字符”记为从 \(v\) 到 \(u\) 的那条边上的字符。还有一条特殊规则:第 \(i\) 回合的字符的 ASCII 码,必须大于等于第 \(i-1\) 回合的字符的 ASCII 码(对于 \(i>1\))。所有回合的编号,在两位玩家之间是连续编号的,也就是说 Max 是奇数编号,Lucas 是偶数编号。无法进行移动的玩家判负。两个弹珠可以同时在同一个顶点。
由于游戏可能持续很久,而 Lucas 和 Max 需要专心寻找 Dart,因此他们没有时间亲自玩,于是请你帮忙判断:若双方都以最优策略行动,谁会获胜?
你需要输出对于所有弹珠初始位置的情况,最终的胜者。
题解
发现 \(n\) 和 \(m\) 不是很大,且题目要求输出对于所有位置情况最终的胜者,考虑记忆化搜索。
设 \(f_{i, j, k}\) 表示当前 Max 弹珠在点 \(i\), Lucas 弹珠在点 \(j\),上一回合移动的边字符为 \(k\) 的状态。
每次 dfs 时,先假设没有任何必胜的走法,将当前状态标记为必败态,再枚举当前玩家的所有合法移动,如果有对手必败局面,则将当前状态标记为必胜态。
Code
#include<bits/stdc++.h>
using namespace std;
const int M = 105;
vector<pair<int, int>>vec[M];
int ret[M][M][30];
int dfs(int i, int j, int k){
if(ret[i][j][k] != 0) return ret[i][j][k];
ret[i][j][k] = -1;
for(auto [v, ch] : vec[i]){
if(ch >= k){
dfs(j, v, ch);
if(ret[j][v][ch] == -1){
ret[i][j][k] = 1;
break;
}
}
}
return ret[i][j][k];
}
int main(){
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i++){
int v, u;
char c;
cin >> v >> u >> c;
vec[v].push_back({u, c - 'a'});
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
char ch;
if(dfs(i, j, 0) == 1) ch = 'A';
else ch = 'B';
printf("%c", ch);
}
printf("\n");
}
return 0;
}
CF1999G2
题目描述
这是一道交互题。
有一把有 \(1001\) 个刻度的尺子,刻度分别为 \(1 \sim 1001\)。不幸的是,尺子丢失了一个刻度 \(x\)(\(2 \le x \le 999\))。当你用尺子量一个长度为 \(y\) 的物体时,尺子量出的结果为:
- 若 \(y < x\),尺子将会量出正确的结果 \(y\)。
- 否则,尺子将会量出错误的结果 \(y + 1\)。
你需要找出丢失的刻度 \(x\)。你可以每次提供两个 \(1\) 至 \(1000\) 内的整数 \(a,b\),你将会收到尺子量出的 \(a\) 的长度与尺子量出的 \(b\) 的长度之积。
你可以进行最多 \(7\) 次询问。
题解
注意到 \(\lceil log_3 V \rceil = 7\) ,考虑三分。
我们每次询问两个三等分点 \(m_1\) 和 \(m_2\),根据答案缩小范围。发现我们每次能够缩小到原范围的 \(\frac{1}{3}\),所以刚好 \(7\) 次即可。
Code
#include<bits/stdc++.h>
using namespace std;
int main(){
int T;
scanf("%d", &T);
while(T--){
int l = 2, r = 999;
int m1, m2;
while(l < r){
m1 = l + (r - l) / 3;
m2 = r - (r - l) / 3;
int op;
printf("? %d %d\n", m1, m2);
fflush(stdout);
scanf("%d", &op);
if(op == (m1 + 1) * (m2 + 1)) r = m1;
else if(op == m1 * (m2 + 1)) l = m1 + 1, r = m2;
else l = m2 + 1;
}
printf("! %d\n", l);
fflush(stdout);
}
return 0;
}
CF493D
题目描述
Vasya 决定学习下棋。但他觉得传统国际象棋没什么意思,于是自己玩起了特殊规则的“象棋”。
皇后可以攻击其所在的行、列以及两条对角线上所有格子。如果某格子与皇后在同一行、列或对角线上,并且该格子上有敌方棋子,皇后就可以移动到该格子并吃掉敌方棋子。若在皇后与该目标棋子之间有其他棋子阻挡,则皇后无法跨越来吃子。
棋盘为 \(n \times n\),棋盘 \((r, c)\) 表示第 \(r\) 行第 \(c\) 列的格子。\((1,1)\) 上有白皇后,\((1,n)\) 上有黑皇后,其余格子全都有绿色卒子,没有归属权。
两位玩家轮流操作。先手玩家使用白皇后,后手玩家使用黑皇后。
每回合玩家必须用自己的皇后吃掉一个棋子(即移动到有绿色卒子或敌方皇后的格子)。若某回合无法用皇后进行任何吃子,则该玩家输;或者如果上回合敌方吃掉了本方皇后,也会输。
现在请你帮 Vasya 判断,如果两位玩家都采取最优策略,\(n\times n\) 棋盘上谁会获胜。
题解
焯水博弈论。感觉这场 D \(\ll\) C。
我们发现两个人不可避免的要往中间移动。因为后手可以模仿先手的行动,所以如果 \(n\) 为奇数时候先手必败。然后如果 \(n\) 为偶数则先手必胜,只要在第一步向右移动即可。然而题目要求:若有多个可选格子,输出 \(r\) 最小的那个;若仍有多个格子,输出 \(c\) 最小的那个,所以肯定走 \((1,2)\) 格,就做完了。
Code
#include<bits/stdc++.h>
using namespace std;
int main(){
int n;
scanf("%d", &n);
if(n % 2 == 0){
printf("white\n1 2");
}else printf("black");
}
CF1068C
题目描述
Ivan 是一名新手画家。他有 \(n\) 种不同颜色的染料。他还确切地知道 \(m\) 对能够和谐搭配的颜色对。
Ivan 还喜欢下国际象棋。他有 \(5000\) 个车。他想取出 \(k\) 个车,将每个车涂成 \(n\) 种颜色中的一种,然后把这 \(k\) 个车放在一个 \(10^{9} \times 10^{9}\) 的棋盘上。
我们称棋盘上的一组车是连通的,如果从任意一个车出发,只通过这组车所在的格子,可以到达这组中的任意其他车。假设车可以“跳过”其他车,也就是说,一个车可以移动到任意与其同行或同列的格子。
Ivan 希望他的车的摆放满足以下条件:
- 对于每种颜色,棋盘上至少有一个这种颜色的车;
- 对于每种颜色,这种颜色的所有车组成的集合是连通的;
- 对于任意两种不同颜色 \(a\) 和 \(b\),如果且仅如果这两种颜色能够和谐搭配,则颜色 \(a\) 和颜色 \(b\) 的所有车的集合的并集是连通的。
请你帮助 Ivan 找到一种满足条件的车的摆放方案。
题解
神秘构造。
既然每种颜色都要出现一次,所以直接往对角线上放,显然互不影响。
然后对于一个关系 \((u, v)\),我们把它放在 \((u, n + i)\) 和 \((v, n+ i)\) 就可以了。因为 \((u, u)\) 和 \((v, v)\) 有车,保证这种颜色的所有车连通。然后我们取的是 \(n + i\),易知这不会对其他的车造成影响。
Code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
int n, m;
cin >> n >> m;
vector<vector<int>> pos(n + 1);
for (int i = 1; i <= n; i++) {
pos[i].emplace_back(i);
}
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
pos[u].emplace_back(n + i);
pos[v].emplace_back(n + i);
}
for (int i = 1; i <= n; i++) {
cout << pos[i].size() << '\n';
for (auto x : pos[i]) {
cout << x << ' ' << i << '\n';
}
}
return 0;
}
CF1381A2
题目描述
有两个长度为 \(n\) 的二进制字符串 \(a\) 和 \(b\)(二进制字符串是仅由 \(0\) 和 \(1\) 组成的字符串)。每次操作,你可以选择 \(a\) 的一个前缀,同时将该前缀中的所有位取反(\(0\) 变为 \(1\),\(1\) 变为 \(0\)),并将该前缀的顺序反转。
例如,如果 \(a=001011\),你选择长度为 \(3\) 的前缀后,\(a\) 变为 \(011011\)。然后如果你选择整个字符串作为前缀,\(a\) 变为 \(001001\)。
你的任务是在最多 \(2n\) 次操作内,将字符串 \(a\) 变换为 \(b\)。可以证明总是存在可行解。
题解
我们想办法把整个字符串变成全部相同,这样就可以一次性完成翻转。这么显然的结论我居然看了20分钟……
Code
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int M = 1e5 + 5;
void sol() {
int n;
scanf("%d\n", &n);
string a, b;
getline(cin, a);
getline(cin, b);
vector<int>ans1, ans2;
for (int i = 0; i < n - 1; i++) {
if (a[i] != a[i + 1])ans1.push_back(i);
if (b[i] != b[i + 1])ans2.push_back(i);
}
if (a[n - 1] == b[n - 1])printf("%d ", ans1.size() + ans2.size());
else printf("%d ", ans1.size() + ans2.size() + 1);
reverse(ans2.begin(), ans2.end());
for (auto v : ans1) printf("%d ", v + 1);
if (a[n - 1] != b[n - 1])printf("%d ", n);
for (auto v : ans2) printf("%d ", v + 1);
printf("\n");
}
int main() {
int T;
scanf("%d", &T);
while (T--) sol();
return 0;
}
CF1043D
题目描述
世界著名侦探 Gawry 先生被指派来查明凶手。他询问了 Ada 的 \(m\) 位邻居,了解在那不幸的一天有哪些客户拜访过她。我们将客户编号为 \(1\) 到 \(n\)。每位邻居的证词是这些编号的一个排列,描述了该邻居看到客户的顺序。
现在他想在每个排列中删除一些前缀和一些后缀(前缀和后缀都可以为空),使得删除后剩下的部分非空且彼此相等——这样一些潜在的嫌疑人可能会被排除,但证词之间就不会互相矛盾了。
他想知道有多少种方法可以做到这一点?如果剩下的公共部分不同,则认为是不同的方法。
题解
可以想到,其实想不到,一个连续子段被所有排列包含,当且仅当其内部的每一对相邻元素在所有排列中都是相邻且顺序相同的。
所以我们存下每行每个数字的位置,然后遍历第一个排列,检查每个相邻对 \((a_i, a_{i + 1})\) 是否在其余 \(m - 1\) 个排列中仍然相邻,根据相邻关系将第一个排列切成若干极长的“合法段”。
对于一个长度为 \(len\) 的段,其所有非空连续子段均合法,贡献为 \(\frac{len \times (len + 1)}{2}\) 个,累加即可。
Code
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int M = 1e5 + 5;
int a[15][M], pos[15][M];
void sol(){
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i++){
for(int j = 1; j <= n; j++){
scanf("%d", &a[i][j]);
pos[i][a[i][j]] = j;
}
}
ll ans = 0, len = 1;
for(int j = 1; j < n; j++){
int x = a[1][j], y = a[1][j + 1];
bool flag = 1;
for(int i = 2; i <= m; i++){
if(pos[i][x] + 1 != pos[i][y]){
flag = 0;
break;
}
}
if(flag) ++len;
else {
ans += len * (len + 1) / 2;
len = 1;
}
}
ans += len * (len + 1) / 2;
printf("%lld", ans);
}
int main(){
sol();
return 0;
}
CF35B
题目描述
注意:需要文件输入输出,输入文件为 input.txt,输出文件为 output.txt
仓库里有一个高为 \(n\),宽为 \(m\) 的架子,即 \(n \times m\) 的一个架子。DravDe 会进行 \(k\) 次操作,每次操作他会往这个架子上的一个位置放进或拿出一个箱子。
如果 DravDe 要放一个箱子,他会先看这个位置上是不是已经有箱子了,如果有则放入右边的位置;如果右边也有箱子,则以此类推。如果到了此行的第 \(m\) 列还有箱子,他会放入下一行的第一列,并重复上述操作。如果到最后都找不到,他会把箱子丢掉,不放到架子上。
如果 DravDe 要拿出一个箱子,你需要输出这个箱子所在的坐标 \((i,j)\),表示这个箱子被放在了第 \(i\) 行第 \(j\) 列,并拿走它。这个箱子可能不存在。
题解
map 的简单应用。
Code
#include<bits/stdc++.h>
using namespace std;
struct T {
int x, y;
bool operator < (const T &b) const {
if (x < b.x) return true;
if (y < b.y && x == b.x) return true;
return false;
}
bool operator == (const T &b) const {
if (y == b.y && x == b.x) return true;
return false;
}
};
map<string, T> mp;
map<T, bool> vis;
int main() {
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
int n, m, k;
cin >> n >> m >> k;
string op, s;
int x, y;
while (k--) {
cin >> op;
if (op == "+1") {
cin >> x >> y >> s;
T op = (T) {x, y};
bool flag = true;
while (vis[op]) {
op.y++;
if (op.y > m) {
op.x++;
op.y = 1;
if (op.x > n) {
flag = false;
break;
}
}
}
if (flag) {
mp[s] = op;
vis[op] = true;
}
} else {
cin >> s;
if (mp.find(s) != mp.end()) {
T op = mp[s];
cout << op.x << " " << op.y << endl;
mp.erase(s);
vis[op] = false;
} else {
cout <<"-1 -1\n";
}
}
}
return 0;
}
CF1881F
题目描述
一棵树有 \(n\) 个点,其中有一些节点被打了标记。保证树是联通且无环的。
定义 $ f_i $ 为第 $ i $ 个节点到所有被标记节点距离的最大值。
你的任务是找出所有点的 $ f_i $ 的最小值。

举个例子,一棵树如上图所示,被标记节点为 \(2\) , \(6\) , \(7\)。 因此 \(f(i) = [2, 3, 2, 4, 4, 3, 3]\)。\(f_i\) 最小的为 \(1\) 和 \(3\) 节点,且最小值为 \(2\)。
题解
好题?
首先我们发现,最小值显然出现在两个最长标记点路径的中点位置,通过手模样例或拉马努金可以想出。
接着是我们如何计算两个最长标记点之间的距离。我们注意到,若一个标记点的子节点没有标记点,则显然是无效的,有点像虚树的处理方式。然后就变成了一个树的直径的问题,两遍 dfs 就可以了。
鉴于我不太会求树的直径,这里给一个简短的解释:
- 任选一个起点 \(s\)。
- 从 \(s\) 开始 DFS,找到距离 \(s\) 最远的一个点 \(p\)。
- 再从 \(s\) 开始 DFS,找到距离 \(p\) 最远的一个点 \(q\)。
- 则 \(p\) 与 \(q\) 之间的路径长度就是树的直径。
Code
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int M = 2e5 + 5;
bool is[M], vis[M];
int d[M], n, k, rt;
vector<int>vec[M];
void dfs1(int u, int fa) {
int res = 0;
for (auto v : vec[u]){
if(v == fa) continue;
dfs1(v, u);
if(vis[v] != 1) res++;
}
if(res == 0 && is[u] == 0) vis[u] = 1;
}
void dfs(int u, int fa){
for(int v : vec[u]){
if(v == fa) continue;
if(vis[v]) continue;
d[v] = d[u] + 1;
if(d[v] > d[rt]) rt = v;
dfs(v, u);
}
}
void sol() {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++) {
vec[i].clear();
d[i] = 0;
is[i] = 0;
vis[i] = 0;
}
for (int i = 1; i <= k; i++) {
int u;
scanf("%d", &u);
is[u] = 1;
rt = u;
}
for (int i = 1; i < n; i++) {
int u, v;
scanf("%d%d", &u, &v);
vec[u].push_back(v);
vec[v].push_back(u);
}
if(k == 1){
printf("0\n");
return;
}
dfs1(rt, 0);
dfs(rt, 0);
d[rt] = 0;
dfs(rt, 0);
printf("%d\n", d[rt] / 2 + (d[rt] & 1));
}
int main() {
int T;
scanf("%d", &T);
while (T--) sol();
return 0;
}
CF1975D
题目描述
378QAQ 有一棵包含 \(n\) 个顶点的树。初始时,所有顶点都是白色的。
树上有两个棋子,分别叫做 \(P_A\) 和 \(P_B\)。\(P_A\) 和 \(P_B\) 分别初始位于顶点 \(a\) 和 \(b\)。每一步,378QAQ 会按如下顺序进行操作:
- 将 \(P_A\) 移动到相邻的一个顶点。如果目标顶点是白色,则将其染成红色。
- 将 \(P_B\) 移动到相邻的一个顶点。如果目标顶点是红色,则将其染成蓝色。
初始时,顶点 \(a\) 被染成红色。如果 \(a=b\),则顶点 \(a\) 被染成蓝色。注意,每一步两个棋子都必须移动。两个棋子可以同时位于同一个顶点。
378QAQ 想知道,将所有顶点都染成蓝色所需的最少步数。
题解
我们想到,先让 \(A\) 和 \(B\) 汇合,然后 \(AB\) 一起遍历整个树,这是最优秀的策略。
于是 \(3\) 遍 dfs 即可。
Code
#include<bits/stdc++.h>
using namespace std;
const int M = 2e5 + 5;
vector<int> vec[M];
int d1[M], d2[M], d3[M];
void dfs(int u, int fa, int *d) {
d[u] = d[fa] + 1;
for (int v : vec[u]) {
if (v == fa) continue;
dfs(v, u, d);
}
}
void sol() {
int n, a, b;
scanf("%d%d%d", &n, &a, &b);
for (int i = 1; i <= n; i++) vec[i].clear();
for (int i = 1; i < n; i++) {
int u, v;
scanf("%d%d", &u, &v);
vec[u].push_back(v);
vec[v].push_back(u);
}
d1[0] = d2[0] = d3[0] = -1;
dfs(a, 0, d1);
dfs(b, 0, d2);
int c = b;
while (d1[c] > d1[b] / 2) {
for (int v : vec[c]) {
if (d1[v] == d1[c] - 1) {
c = v;
break;
}
}
}
dfs(c, 0, d3);
int maxd = 0;
for (int i = 1; i <= n; i++)
if (d3[i] > maxd) maxd = d3[i];
int m = max(d1[c], d2[c]);
int ans = m + 2 * (n - 1) - maxd;
printf("%d\n", ans);
}
int main() {
int T;
scanf("%d", &T);
while (T--) sol();
return 0;
}
CF1329B
题目描述
有两个整数 \(d, m\),找到这样的数列 \(a\) 的数列,满足以下限制条件:
- 数列 \(a\) 的长度为 \(n\),\(n \ge 1\);
- \(1 \le a_i \lt a_2 \lt \cdots \lt a_n \le d\);
- 定义一个长度为 \(n\) 的数组 \(b\):\(b_1 = a_1\),\(\forall i \ge 1, b_i = b_{i - 1} \oplus a_i\),其中 \(\oplus\) 表示二进制异或 (xor)。在构建出 \(b\) 后,应当满足 \(b_1 \lt b_2 \lt \cdots \lt b_{n - 1} \lt b_n\) 的限制条件。
由于满足条件的数列数量可能很多,请输出答案模 \(m\) 的结果。
题解
因为 \(b_i = b_{i - 1} \oplus a_i\),所以 \(a_i = b_{i - 1} \oplus b_i\),由于 \(b_1 < b_2 < ⋯ < b_n\),每个 \(b_i\) 的最高二进制位一定严格递增,故 \(a_i\) 的最高二进制位一定严格递增。
这里就把题目巧妙转化为:从 \(1∼d\) 中选出若干个数,要求它们的最高位两两不同(即每个二进制长度至多选一个数),且至少选一个数。求方案数。
我们发现,第 \(k\) 位包含的数区间为 \( [2^k, \min(d, 2^{k+1}−1) ]\),该组元素个数 \(cnt_k = \min(d, 2^{k+1}−1) - 2^k\),所以总元素个数为 \(\prod\limits_{k} (cnt_k + 1) - 1\)。
Code
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
void sol(){
ll d, m;
scanf("%lld%lld", &d, &m);
if(m == 1){
printf("0\n");
return;
}
ll ans = 1;
for(ll k = 0; (1 << k) <= d; k++){
ll lf = 1 << k;
ll rt = min(d, (1 << (k + 1)) - 1ll);
ans = ans * (rt - lf + 2) % m;
}
ans = (ans - 1 + m) % m;
printf("%lld\n", ans);
}
int main(){
int T;
scanf("%d", &T);
while(T--) sol();
return 0;
}
CF1884C
题目描述
数组 \(a_1, a_2, \ldots, a_m\) 初始全部为 \(0\)。给定 \(n\) 个两两不同的区间 \(1 \le l_i \le r_i \le m\)。你需要从这些区间中任选一个子集(可以为空集)。接下来,进行如下操作:
- 对于每个 \(i = 1, 2, \ldots, n\),如果区间 \((l_i, r_i)\) 被选入子集,则对于每个 \(l_i \le j \le r_i\),将 \(a_j\) 加 \(1\)(即 \(a_j\) 变为 \(a_j + 1\))。如果区间 \((l_i, r_i)\) 没有被选中,则数组不变。
- 接着(在处理完所有 \(i = 1, 2, \ldots, n\) 后),计算 \(a\) 中的最大值 \(\max(a)\) 和最小值 \(\min(a)\)。
- 最后,所选区间子集的代价定义为 \(\max(a) - \min(a)\)。
请你求出所有区间子集的最大代价。
题解
这道题的关键突破口是两个结论,直接将复杂问题简化为简单的差分统计:
- 最小值恒为 0:我们可以不选任何覆盖某个位置的区间,让该位置的值为 0,因此
min(a) = 0,问题转化为最大化max(a)。 - 全区间线段无效:若一个线段覆盖整个区间
[1, m],选它会让所有位置 +1,最大值和最小值同步增加,差值不变。因此直接丢弃所有覆盖[1, m]的线段。 - 不能同时选 左端点为 1 的线段 和 右端点为 m 的线段,必须二选一:
- 选所有
左端点=1+ 普通线段 - 选所有
右端点=m+ 普通线段 - 最终答案取两种方案的最大值。
- 选所有
由于 m 最大为 1e9,无法直接开数组,因此用离散化 + 差分解决范围问题。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 200005;
struct node {
int l, r;
} a[N];
int n, m;
vector<int> vec;
int find(int x) {
return lower_bound(vec.begin(), vec.end(), x) - vec.begin() + 1;
}
bool cmp(node a, node b) {
if (a.l == b.l) return a.r < b.r;
return a.l < b.l;
}
int sol(int op) {
vec.clear();
int tot = 0;
for (int i = 1; i <= n; i++) {
int l = a[i].l, r = a[i].r;
if (l == 1 && r == m) continue;
if (op == 1 && r == m) continue;
if (op == 2 && l == 1) continue;
vec.push_back(l);
vec.push_back(r + 1);
tot++;
}
sort(vec.begin(), vec.end());
vec.erase(unique(vec.begin(), vec.end()), vec.end());
vector<int> c(vec.size() + 2, 0);
int cnt = 0;
for (int i = 1; i <= n; i++) {
int l = a[i].l, r = a[i].r;
if (l == 1 && r == m) continue;
if (op == 1 && r == m) continue;
if (op == 2 && l == 1) continue;
int lf = find(l);
int rt = find(r + 1);
c[lf]++;
c[rt]--;
}
int ans = 0, now = 0;
for (int i = 1; i <= vec.size(); i++) {
now += c[i];
ans = max(ans, now);
}
return ans;
}
void sol() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d%d", &a[i].l, &a[i].r);
}
int res1 = sol(1);
int res2 = sol(2);
printf("%d\n", max(res1, res2));
}
int main() {
int T;
scanf("%d", &T);
while (T--) sol();
return 0;
}
CF2039D
题目描述
Shohag 拥有一个整数 \(n\) 和一个包含 \(m\) 个不同整数的集合 \(S\)。请帮助他找到字典序最大*的整数数组 \(a_1, a_2, \ldots, a_n\),使得对于每个 \(1 \le i \le n\) 有 \(a_i \in S\) ,并且满足对所有 \(1 \le i < j \le n\) 有 \(a_{\gcd(i, j)} \neq \gcd(a_i, a_j)\),或者说明不存在这样的数组。
*一个数组 \(a\) 如果在第一个不同的位置上比数组 \(b\) 有更大的元素,则称其为字典序大于数组 \(b\)(假设两个数组长度相同)。
题解
神秘构造。
\(\gcd\) 在下标里面不好搞,考虑转化。
显然 当 \(i\mid j\) 时, \(a_i \nmid a_j\),反之同理。然后我们有注意到,\(a_1\) 是填最大值的,因为 \(1\) 和所有的数字 \(\gcd\) 都是 \(1\)。
于是我们素数筛,令 \(f_i\) 为 第 \(i\) 位置放置第几大的数,如果 \(j \mid i\),那有 \(f_i = \max(f_i, f_{i/j} + 1)\)。预处理即可。无解就是 \(m > \max(a_1,\cdots,a_n)\)。
Code
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int M = 3e5 + 5;
int a[M], f[M], n, m;
void init(int T) {
f[1] = 1;
for(int i = 2; i <= T; i++){
int tmp = i;
for(int j = 2; j * j <= tmp; j++){
if(tmp % j == 0){
f[i] = max(f[i], f[i / j] + 1);
while(tmp % j == 0) tmp /= j;
}
}
if(tmp > 1) f[i] = max(f[i], f[i / tmp] + 1);
}
}
bool cmp(int x, int y){
return x > y;
}
void sol() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
scanf("%d", &a[i]);
}
sort(a + 1, a + 1 + m, cmp);
int mx = *max_element(f + 1, f + 1 + n);
if (mx > m) {
printf("-1\n");
return;
}
for(int i = 1; i <= n; i++){
printf("%d ", a[f[i]]);
}
printf("\n");
}
int main() {
int T;
scanf("%d", &T);
init(M - 2);
while (T--) sol();
return 0;
}
CF1336B
题目描述
最近,Xenia 买了 \(n_r\) 颗红宝石、\(n_g\) 颗绿宝石和 \(n_b\) 颗蓝宝石。每颗宝石都有一个重量。
现在,她准备挑选三颗宝石。
Xenia 喜爱色彩斑斓的事物,所以她会恰好各选一颗不同颜色的宝石。
Xenia 也追求平衡,因此她会尽量选择重量差异最小的宝石。
具体来说,假设选中的三颗宝石的重量分别为 \(x\)、\(y\) 和 \(z\),Xenia 希望找到 \((x-y)^2+(y-z)^2+(z-x)^2\) 的最小值。作为她的好朋友,你能帮帮她吗?
题解
注意到我们尽量想让 \(x,y,z\) 靠的稍微近一些,所以我们枚举 \(x,y,z\) 的大小关系,然后在数组中用 lower_bound 找和 \(x\) 最接近的两个数来计算,并且取最小值。
Code
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
ll calc(ll x, ll y, ll z) {
return (x - y) * (x - y) + (y - z) * (y - z) + (z - x) * (z - x);
}
void update(vector<int>& a, vector<int>& b, vector<int>& c, ll &ans) {
for (int y : a) {
auto p1 = lower_bound(b.begin(), b.end(), y);
if (p1 != b.end()) {
int x = *p1;
auto p2 = lower_bound(c.begin(), c.end(), y);
if (p2 != c.end()) ans = min(ans, calc(x, y, *p2));
if (p2 != c.begin()) ans = min(ans, calc(x, y, *prev(p2)));
}
if (p1 != b.begin()) {
int x = *prev(p1);
auto p2 = lower_bound(c.begin(), c.end(), y);
if (p2 != c.end()) ans = min(ans, calc(x, y, *p2));
if (p2 != c.begin()) ans = min(ans, calc(x, y, *prev(p2)));
}
}
}
void sol() {
int n, m, k;
scanf("%d%d%d", &n, &m, &k);
vector<int> r(n), g(m), b(k);
for (int i = 0; i < n; i++) scanf("%d", &r[i]);
for (int i = 0; i < m; i++) scanf("%d", &g[i]);
for (int i = 0; i < k; i++) scanf("%d", &b[i]);
sort(r.begin(), r.end());
sort(g.begin(), g.end());
sort(b.begin(), b.end());
ll ans = 9e18;
update(r, g, b, ans);
update(g, b, r, ans);
update(b, r, g, ans);
printf("%lld\n", ans);
}
int main() {
int T;
scanf("%d", &T);
while (T--) sol();
return 0;
}
CF1557C
题目描述
Moamen 和 Ezzat 正在玩一个游戏。他们创建了一个长度为 \(n\) 的非负整数数组 \(a\),其中每个元素都小于 \(2^k\)。
如果满足 \(a_1 \,\&\, a_2 \,\&\, a_3 \,\&\, \ldots \,\&\, a_n \ge a_1 \oplus a_2 \oplus a_3 \oplus \ldots \oplus a_n\),则 Moamen 获胜。
请计算有多少种数组 \(a\) 能让 Moamen 获胜。
由于答案可能非常大,请输出结果对 \(10^9+ 7\) 取模后的值。
题解
注意到我们需要按位操作,考虑二进制。
当 \(n\) 为奇数时,考虑在第 \(i\) 位:
- 全为 \(1\) 的方案:有 \(1\) 种。
- 不全为 \(1\) 且 XOR = AND = 0:即在 \(n\) 个位置上选偶数个 \(1\) ,总方案数为 \(2^{n - 1}\)。
总方案数为 \((2^{n-1} + 1)^k\)。
当 \(n\) 为偶数时,同样考虑在第 \(i\) 位:
- 该位:必须全为 \(1\) ,方案有 \(1\) 种。
- 更高位:必须保持 \(A = B\) ,所以这些位只能取偶数个 1 且不全为 1,总方案数为 \(2^{n - 1} - 1\)。
- 更低位:可以完全任意选择,每一位有 \(2^n\) 种填法。
另外,还要加上所有位都相等的情况:所有位都满足 \(A = B\),即每一位都为偶数个 1 且不全为 1,方案数为 \((2^{n - 1} - 1)^k\)。所以总方案数为 \((2^{n - 1} - 1)^k + \sum\limits_{t = 1}^k(2^{n - 1 - 1})^{k - t} \cdot (2^n)^{t - 1}\)。
Code
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int mod = 1e9 + 7;
ll qpow(ll x, ll y){
ll res = 1;
while(y){
if(y & 1) res = res * x % mod;
x = x * x % mod;
y >>= 1;
}
return res;
}
void sol(){
int n, k;
ll ans;
scanf("%d%d", &n, &k);
if(n % 2 == 1){
ans = qpow(qpow(2, n - 1) + 1, k);
} else {
ans = qpow(qpow(2, n - 1) - 1, k);
ll res = 0;
for(int i = 1; i <= k; i++){
res += qpow(qpow(2, n - 1) - 1, k - i) * qpow(qpow(2, n), i - 1) % mod;
}
ans = (ans + res) % mod;
}
printf("%lld\n", ans);
}
int main(){
int T;
scanf("%d", &T);
while(T--) sol();
return 0;
}
CF729D
题目描述
Galya 正在一个 \(1 \times n\) 的网格上玩一维“海战”游戏。在这个游戏中,\(a\) 艘战舰被放置在网格上。每艘战舰都占据连续的 \(b\) 个格子。一个格子不能同时属于两艘船,但是船只之间可以相互接触。
Galya 并不知道船的位置。她可以向某些格子射击,每射击一次,会告知她该格子是否有船(这种情况称为“命中”),否则称为“未命中”。
Galya 已经进行了 \(k\) 次射击,所有的结果都是未命中。
你的任务是计算,Galya 至少还需要再向多少个格子射击,才能保证无论船如何放置,至少能够命中一艘船。
保证至少存在一种合理的舰船摆放方式。
题解
我们首先把所有连续的 \(0\) 串位置放进一个队列中,显然这些格子可能存在船。
然后对于一个长度为 \(len\) 的区间,显然需要 \(\lfloor \frac{len}{b}\rfloor\) 次打击可以找到 \(\lfloor \frac{len}{b}\rfloor\) 条船。因为我们可以贪心的去打重叠最多次的那个点。
至于保证无论船如何放置,至少能够命中一艘船的限制,我们假设一共可能有 \(tot\) 条船,打 \(tot - a + 1\) 条就可以了。
Code
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
int main(){
int n, a, b, k;
scanf("%d%d%d%d\n", &n, &a, &b, &k);
string s;
getline(cin, s);
queue<pair<int, int>> qu;
int lst = 0;
for(int i = 1; i <= n; i++){
if(s[i - 1] == '1') {
if(i - 1 > lst) {
qu.push(make_pair(lst + 1, i - 1));
}
lst = i;
}
}
if(n > lst) {
qu.push(make_pair(lst + 1, n));
}
int tot = 0;
vector<int> ans;
while (!qu.empty()) {
auto [l, r] = qu.front();
qu.pop();
int len = r - l + 1;
int c = len / b;
tot += c;
for (int j = 1; j <= c; j++) {
ans.push_back(l + j * b - 1);
}
}
printf("%d\n", tot - a + 1);
for (int i = 0; i < tot - a + 1; i++) {
printf("%d ", ans[i]);
}
return 0;
}
CF1779D
题目描述
Boris 认为国际象棋是一项无聊的游戏。因此,他提前离开了比赛,去理发店理发,因为他的头发有点乱。
他当前的头发可以用一个数组 \(a_1,a_2,\ldots,a_n\) 来描述,其中 \(a_i\) 表示第 \(i\) 个位置头发的高度。他理想的发型可以用一个数组 \(b_1,b_2,\ldots,b_n\) 以类似的方式描述。
理发师有 \(m\) 把剃刀。每把剃刀有自己的尺寸,并且每把剃刀最多只能使用一次。在一次操作中,他选择一把剃刀并修剪 Boris 的一段头发。更正式地说,一次操作为:
- 选择一把尚未使用过的剃刀,设其尺寸为 \(x\);
- 选择一个区间 \([l,r]\)(\(1\leq l \leq r \leq n\));
- 对于每个 \(l\leq i \leq r\),将 \(a_i := \min(a_i, x)\);
注意,有些剃刀可能尺寸相同——理发师只能使用尺寸为 \(x\) 的剃刀不超过该尺寸剃刀的数量。
他可以进行任意多次操作,只要每把剃刀最多使用一次,并且最后满足 \(a_i = b_i\) 对于每个 \(1 \leq i \leq n\)。他不必使用所有剃刀。
你能判断理发师是否能将 Boris 的头发修剪成他想要的样子吗?
题解
首先操作只能让值变小,如果某个位置初始就比目标小,那永远也达不到。
然后用 map 记录每种尺寸的剃刀各有多少把。
注意到大尺寸的剃刀不能覆盖目标值比自己大的位置,否则会把那些位置压过头。
所以,所有 \(b_j > v\) 的位置会把数组分成若干连续段。尺寸为 \(v\) 的剃刀,其作用范围只能在一个段内使用。
用 set 存储这些墙的下标,初始插入虚拟边界 \(0\) 和 \(n + 1\)。
我们从大到小遍历目标值,每次处理完一个值 \(v\),就把它的所有位置也变成墙(因为它们对后面更小的尺寸来说也是“不可跨越”的)。
遍历到值 \(v\) 时:
- 对于当前 \(v\) 的每个位置,用 upper_bound 找到它右侧第一个墙。
- 如果 \(a_i > b_i\) 且 \(b_i = v\),说明这个段里至少有一处需要用尺寸 \(v\) 的剃刀。
- 同一个段无论有多少个需要修剪的位置,都只需要 一把尺寸为 \(v\) 的剃刀。
- 遍历完当前值的所有位置后,把它们全部加入 set。
最终 \(need_v\) 就表示尺寸 \(v\) 的剃刀最少需要多少把。与 map 中的值比较即可。
Code
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int M = 3e5 + 5;
int a[M], b[M], c[M];
void sol() {
int n, m;
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
int flag = 0;
for (int i = 1; i <= n; i++){
scanf("%d", &b[i]);
if(a[i] < b[i]) flag = 1;
}
scanf("%d", &m);
for (int i = 1; i <= m; i++) scanf("%d", &c[i]);
if(flag) {
printf("NO\n");
return;
}
map<int, int> mp;
for (int i = 1; i <= m; i++) mp[c[i]]++;
map<int, vector<int>> pos;
for (int i = 1; i <= n; i++) {
pos[b[i]].push_back(i);
}
set<int> st;
st.insert(0);
st.insert(n + 1);
map<int, int> need;
for (auto it = pos.rbegin(); it != pos.rend(); ++it) {
int v = it->first;
vector<int> &vec = it->second;
set<int> vis;
for (int i : vec) {
if (a[i] > b[i]) {
auto itr = st.upper_bound(i);
int L = *prev(itr);
if (vis.find(L) == vis.end()) {
need[v]++;
vis.insert(L);
}
}
}
for (int i : vec) {
st.insert(i);
}
}
for (auto &p : need) {
int v = p.first, cnt = p.second;
if (mp[v] < cnt) {
flag = 1;
break;
}
}
if (flag) printf("NO\n");
else printf("YES\n");
}
int main() {
int T;
scanf("%d", &T);
while (T--) sol();
return 0;
}
CF630I
题目描述
停车场共有 \(2n-2\) 个停车位。共有 \(4\) 种品牌的汽车,每种汽车的数量都远大于停车位的数量。
该公司首席执行官认为,如果停车场有 恰好 \(n\) 个连续汽车的品牌相同,则停车场会更漂亮。
给定 \(n\) 的值,问有多少的方案使停车场满足条件。
题解
考虑把这连续 \(n\) 个放在哪里,如果放在边上就是 \(4 \times 3 \times 4^{n - 3}\),如果放在中间就是 \(3^2 \times 4 \times 4^{n - 4}\)。
Code
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
ll qpow(ll x, ll y) {
if(y == 0) return 1ll;
ll res = 1;
while (y) {
if (y & 1) res = x * res;
x = x * x;
y >>= 1;
}
return res;
}
int main(){
int n;
cin >> n;
ll ans = 0;
for(int r = n; r <= 2 * n - 2; r++){
int l = r - n + 1;
if(l == 1 || r == 2 * n - 2){
ans += 12 * qpow(4, n - 3);
} else {
ans += 36 * qpow(4, n - 4);
}
}
cout << ans;
return 0;
}
CF1561D1
题目描述
你有一个由 \(n\) 个格子组成的竖直条带,这些格子从上到下依次编号为 \(1\) 到 \(n\)。
你还有一个棋子,初始时放在第 \(n\) 个格子上。你需要不断地将棋子向上移动,直到它到达第 \(1\) 个格子。
假设某一时刻棋子位于第 \(x\) 个格子(\(x > 1\))。每次移动棋子可以有以下两种方式之一:
- 减法操作:你选择一个整数 \(y\),满足 \(1 \le y \le x-1\),然后将棋子从第 \(x\) 个格子移动到第 \(x-y\) 个格子。
- 向下取整除法操作:你选择一个整数 \(z\),满足 \(2 \le z \le x\),然后将棋子从第 \(x\) 个格子移动到第 \(\lfloor \frac{x}{z} \rfloor\) 个格子(即 \(x\) 除以 \(z\) 向下取整)。
请你计算,将棋子从第 \(n\) 个格子移动到第 \(1\) 个格子的所有不同方案数(每次至少要移动一次),并将结果对 \(m\) 取模后输出。注意,如果存在多种方式能在一次移动中将棋子从一个格子移动到另一个格子,则这些方式都视为不同的方案(参见样例解释以加深理解)。
题解
注意到 \(i\) 点通过除法可以转移的位置是 \(\lfloor\frac i2 \rfloor, \lfloor\frac i3 \rfloor, \cdots, 1\),这很像整除分块。直接维护即可。
至于减法到达 \(i\),维护一下后缀和,显然这个点往后的点都可以转移到它。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 2e5 + 5;
int f[M];
signed main() {
int n, m;
cin >> n >> m;
f[n] = 1;
for (int i = 1; i <= n - 1; i++) f[i] = 0;
int sum = 1, l = 2, r;
for (; l <= n; l = r + 1) {
r = n / (n / l);
f[n / l] = (f[n / l] + (r - l + 1) * f[n]) % m;
}
for (int i = n - 1; i >= 1; i--) {
f[i] = (f[i] + sum) % m;
l = 2;
for (; l <= i; l = r + 1) {
r = i / (i / l);
f[i / l] = (f[i / l] + (r - l + 1) * f[i]) % m;
}
sum = (sum + f[i]) % m;
}
cout << f[1];
return 0;
}
CF98A
题目描述
来自遥远王国的聪明的瓦西莉萨收到了她的朋友——来自更遥远王国的聪明的赫尔加赠送的礼物。这个礼物是一个惊喜盒,但瓦西莉萨还不知道这个惊喜究竟是什么,因为她无法打开盒子。她希望你能帮她打开它。
盒子的锁是这样设计的。盒子本身是一个完美的黑色立方体,每个面上都有一个完全相同的凹槽(这是一种远方王国的纳米科技,连那里的科学家都还没梦想过)。盒子配有六颗宝石,这些宝石的形状与盒子每个面的凹槽完全吻合。只有在盒子被正确装饰后,才能打开,也就是说,每个凹槽里恰好放入一颗宝石。如果通过随意旋转盒子,两种装饰方式能够相互转化,则认为这两种装饰方式是相同的(请注意,这个盒子是一个完美的纳米科技立方体)。
现在,瓦西莉萨想知道,给定一组颜色后,最糟糕情况下她需要尝试多少种不同的方法来装饰盒子才能打开它?你需要注意,同一种颜色的宝石是不可区分的。请帮助瓦西莉萨解决这个富有挑战性的问题。
题解
注意到只有 \(6\) 种颜色,我们直接暴力打表,算出 \(11\) 种拆分方式下的方案数就行了。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int mp[8];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
string s;
cin >> s;
for (int i = 0; i <= 5; i++) {
if (s[i] == 'R') mp[1]++;
if (s[i] == 'O') mp[2]++;
if (s[i] == 'Y') mp[3]++;
if (s[i] == 'G') mp[4]++;
if (s[i] == 'B') mp[5]++;
if (s[i] == 'V') mp[6]++;
}
sort(mp + 1, mp + 7);
int sum = 0;
for (int i = 1; i <= 6; i++) sum = sum * 10 + mp[i];
if (sum == 6) cout << 1;
if (sum == 15) cout << 1;
if (sum == 24) cout << 2;
if (sum == 33) cout << 2;
if (sum == 114) cout << 2;
if (sum == 123) cout << 3;
if (sum == 222) cout << 6;
if (sum == 1113) cout << 5;
if (sum == 1122) cout << 8;
if (sum == 11112) cout << 15;
if (sum == 111111) cout << 30;
return 0;
}

浙公网安备 33010602011771号