A.Closest Point(思维)
题意:
考虑直线上的一组点。两点iii与jjj之间的距离为∣i−j∣|i-j|∣i−j∣。
如果集合中没有其他点kkk使得jjj到kkk的距离严格小于jjj到iii的距离,则集合中的点iii与集合中的点jjj的距离最近。换句话说,集合中所有其他点到jjj的距离都大于或等于∣i−j∣|i-j|∣i−j∣。
例如,考虑一组点{1,3,5,8}\{1,3,5,8\}{1,3,5,8}:
- 对于点111,最近的点是333(其他点的距离大于∣1−3∣=2|1-3|=2∣1−3∣=2);
- 对于点333来说,有两个最近点:111和555;
- 对于点555,最近的点是333(而不是888)。(但不是888,因为它的距离大于∣3−5∣|3-5|∣3−5∣);
- 对于点888,最近的点是555。
给你一组点。你必须在这个集合中加入一个整数点,使它与集合中现有的每一个点都不同,并使它成为离集合中每一个点最近的点。这可能吗?
分析:
模拟题意可以发现:
- n>2n>2n>2时,恒不成立
- n=2n=2n=2时,若两个数不相邻,则成立
- n=1n=1n=1时,恒成立
分情况讨论即可
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 100;
int a[N];
void solve() {
int n;
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
if (n > 2)
cout << "NO" << endl;
else {
if (n == 1 || a[2] - a[1] != 1)
cout << "YES" << endl;
else
cout << "NO" << endl;
}
}
int main() {
int T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
B.Game with Doors(思维)
题意:
一排有100100100个房间,房间之间有999999扇门;第iii扇门连接着iii和i+1i+1i+1两个房间。每扇门既可以上锁,也可以不上锁。最初,所有的门都没有上锁。
如果房间yyy和房间xxx之间的所有门都没有上锁,我们就说房间yyy可以从房间xxx到达yyy。
你知道吗?
- 爱丽丝在[l,r][l,r][l,r]的某个房间里;
- 鲍勃位于[L,R][L,R][L,R]中的某个房间;
- 爱丽丝和鲍勃在不同的房间。
但是,你并不知道他们具体在哪个房间。
你不想让爱丽丝和鲍勃接触到对方,所以你要锁上一些门来防止他们接触到对方。无论爱丽丝和鲍勃在给定段落中的起始位置如何,要使他们不能相遇,你必须锁上的门的最小数目是多少?
分析:
本题我们仍然分情况讨论:
- 两人没有相交的区域,那么只需要关闭一扇门即可;
- 两人完全重合,那么这些门需要全部关闭,即r−lr−lr−l;
- 左边界或者右边界有重合,以右边界重合为例,找出他们两左边界的最大值ans=r−max(l,L)+1ans=r−max(l,L)+1ans=r−max(l,L)+1,首先rrr到max(l,L)max(l,L)max(l,L)的门必须关闭,那么假设l<Ll<Ll<L,对LLL到L−1L-1L−1和LLL,两人会有交界,这个门也需要关闭);
- 两人相交无重合,ans=min(r,R)−max(l,L)+2ans=min(r,R)−max(l,L)+2ans=min(r,R)−max(l,L)+2,重合的部分需要关闭且重合的边界也需要关闭;
代码:
#include<bits/stdc++.h>
using namespace std;
void solve() {
int a, b;
cin >> a >> b;
int l, r;
cin >> l >> r;
if (b < l || r < a) {
cout << 1 << endl;
return;
}
if (a == l && b == r) {
cout << b - a << endl;
return;
}
int k = max(a, l);
int s = min(b, r);
if (a == l || b == r) {
cout << s - k + 1 << endl;
return;
} else {
cout << s - k + 2 << endl;
return;
}
}
int main() {
int T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
C.Splitting Items(枚举)
题意:
爱丽丝和鲍勃有nnn件物品想平分,于是他们决定玩一个游戏。所有物品都有成本,第iii件物品的成本为aia_iai。玩家从爱丽丝开始轮流移动。
在每个回合中,玩家从剩下的物品中选择一个并拿走。游戏一直进行到没有物品为止。
假设AAA是爱丽丝拿走物品的总费用,BBB是鲍勃拿走物品的总费用。这样,游戏的得分就等于A−BA-BA−B。
爱丽丝希望得分最大化,而鲍勃希望得分最小化。爱丽丝和鲍勃都将以最优方式进行博弈。
但游戏将在明天进行,所以今天鲍勃可以稍微修改一下成本。他可以将几个(可能一个也没有,也可能全部)项目的成本aia_iai增加一个整数值(可能每个项目增加相同的值,也可能增加不同的值)。但是,增加的总金额必须小于或等于kkk。否则,爱丽丝可能会怀疑。请注意,鲍勃不能减少成本,只能增加成本。
鲍勃能得到的最低分数是多少?
分析:
很显然从大到小排序则若为第偶数个元素,归鲍勃,否则归爱丽丝。显然鲍勃只能让自己的元素的值和在它前面一个位置的元素的值尽量的接近的时候最优。因此直接暴力枚举鲍勃的每一个元素计算其可以最多加多少,贪心计算即可。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1000005;
int a[N];
void solve() {
int n, k;
cin >> n >> k;
for (int i = 1; i <= n; i++)
cin >> a[i];
sort(a + 1, a + n + 1, greater<int>());
int cnt = 0;
for (int i = 2; i <= n; i += 2) {
int diff = a[i - 1] - a[i];
diff = min(diff, k);
a[i] += diff;
k -= diff;
}
sort(a + 1, a + n + 1, greater<int>());
for (int i = 1; i <= n; i++) {
if (i & 1)
cnt += a[i];
else
cnt -= a[i];
}
cout << cnt << endl;
}
int main() {
int T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
D.Colored Portals(思维)
题意:
直线上有nnn座城市。这些城市的编号从111到nnn。
传送门用于在城市之间移动。传送门有444种颜色:蓝色、绿色、红色和黄色。每个城市都有两种不同颜色的传送门。如果城市iii和城市jjj的传送门颜色相同,你就可以在这两个城市之间移动(例如,你可以在"蓝-红"城市和"蓝-绿"城市之间移动)。这种移动需要花费∣i−j∣|i-j|∣i−j∣枚金币。
你的任务是回答qqq个独立的问题:计算从城市xxx移动到城市yyy的最小花费。
分析:
本题我们考虑分类讨论,发现在大多数情况下给定的询问x,yx,yx,y均可以直达,此时的代价即为∣x−y∣|x−y|∣x−y∣。
无法直达的情况x,yx,yx,y所在城市的种类只有三种:BG和RY,BR和GY,BY和GR。容易发现对于这三种情况,仅需找到任意一个类型与它们均不同的城市ppp,就可以作为中转点使得x,yx,yx,y可互相到达,代价即为∣x−p∣+∣y−p∣|x−p|+|y−p|∣x−p∣+∣y−p∣。
若中转城市恰好位于[x,y][x,y][x,y]中则代价即为∣x−y∣|x−y|∣x−y∣,否则仅需检查距离x,yx,yx,y最近的中转城市即可。于是考虑按照位置升序维护每种城市的位置,对于每次询问先判断x,yx,yx,y是否可以直达,若无法直达则枚举中转城市的类型,检查[x+1,y−1][x+1,y−1][x+1,y−1]中有无中转城市,若没有则在[1,x−1],[y+1,n][1,x−1],[y+1,n][1,x−1],[y+1,n]中二分找到最优的中转城市即可。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
const int INF = 1e9;
int n, q, t[N], cnt[7][N];
map<string, int> mp;
vector<int> pos[7];
int check(int tmp) {
return (tmp <= 3 ? tmp + 3 : tmp - 3);
}
void solve() {
cin >> n >> q;
for (int i = 1; i <= 6; ++i)
pos[i].clear();
for (int i = 1; i <= n; ++i) {
string s;
cin >> s;
t[i] = mp[s];
pos[t[i]].push_back(i);
for (int j = 1; j <= 6; ++j)
cnt[j][i] = cnt[j][i - 1];
cnt[t[i]][i]++;
}
while (q--) {
int x, y;
cin >> x >> y;
if (x > y)
swap(x, y);
if (check(t[x]) != t[y]) {
cout << abs(x - y) << endl;
continue;
}
int ans = INF;
for (int i = 1; i <= 6; ++i) {
if (i == t[x] || i == t[y])
continue;
if (cnt[i][y - 1] - cnt[i][x] != 0)
ans = y - x;
if (cnt[i][x - 1]) {
int p = lower_bound(pos[i].begin(), pos[i].end(), x) - pos[i].begin();
--p;
ans = min(ans, x - pos[i][p] + y - pos[i][p]);
}
if (cnt[i][n] - cnt[i][y]) {
int p = lower_bound(pos[i].begin(), pos[i].end(), y) - pos[i].begin();
ans = min(ans, pos[i][p] - y + pos[i][p] - x);
}
}
if (ans >= INF)
ans = -1;
cout << ans << endl;
}
}
int main() {
mp["BG"] = 1;
mp["BR"] = 2;
mp["BY"] = 3;
mp["RY"] = 4;
mp["GY"] = 5;
mp["GR"] = 6;
int T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
E.Not a Nim Problem(博弈)
题意:
爱丽丝和鲍勃正在玩一个游戏。他们有nnn堆棋子,其中第iii堆最初包含aia_iai个棋子。
在他们的回合中,玩家可以选择任意一堆石子,并从中取出任意正数的石子,但有一个条件:
- 让该堆棋子的当前数量为xxx。不允许从石堆中取出yyy个数的石子,使得xxx和yyy的最大公约数不等于111。
无法下棋的棋手输棋。两位棋手都以最佳状态下棋(也就是说,如果一位棋手的策略能让他获胜,那么无论对手如何回应,他都会获胜)。爱丽丝先下。
确定谁会赢。
分析:
读题发现就是标准NimNimNim游戏,考虑如何求SGSGSG函数。
简单模拟一下可以发现,对于单堆石子当且仅当个数xxx为奇数时先手必胜,此时仅需取走x−2x−2x−2个石子即可。然后考虑通过对1∼ai1∼a_i1∼ai求SGSGSG函数拓展到多堆石子即可。
因为互质对的数量级很大,直接枚举转移求SGSGSG函数显然会超时。观察数据范围发现线性筛似乎可以跑,考虑寻找每个值与其最小质因子的关系。推导发现:
- SG(0)=0SG(0)=0SG(0)=0,
- SG(1)=mex0=1SG(1)=mex{0}=1SG(1)=mex0=1。
- 对于所有偶数xxx,SG(x)=0SG(x)=0SG(x)=0。
- 对于所有大于222的质数ppp,对任意1≤i<p1\le i\lt p1≤i<p,gcd(p,i)=1gcd(p,i)=1gcd(p,i)=1,则SG(p)=mex[1,2,⋯,p−1]=rank(p)SG(p)=mex[1,2,⋯,p−1]=rank(p)SG(p)=mex[1,2,⋯,p−1]=rank(p),rank(p)rank(p)rank(p)表示ppp在所有质数(包括222)中的排名。
- 对于所有奇数xxx,设其最小质因子为ppp,发现有gcd(x,x−p)≠0gcd(x,x−p)≠0gcd(x,x−p)=0,且对任意1≤y<p1\le y\lt p1≤y<p,gcd(i,x)=1gcd(i,x)=1gcd(i,x)=1。则xxx第一个不能转移到的位置是ppp。通过观察转移方式可以发现SG(x)=SG(p)SG(x)=SG(p)SG(x)=SG(p)。
由此使用线性筛,对于每个数求得其最小质因子,即可求得所有SGSGSG函数,则仅需判断SG(a1)⊕⋯⊕SG(an)≠0SG(a_1)⊕⋯⊕SG(a_n)≠0SG(a1)⊕⋯⊕SG(an)=0是否成立即可。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e7 + 10;
int n;
bool vis[N];
int pnum, p[N], sg[N];
void Getprime() {
n = 1e7;
sg[0] = 0;
sg[1] = 1;
for (int i = 2; i <= n; ++i) {
if (i % 2 == 0)
sg[i] = 0;
if (!vis[i]) {
p[++pnum] = i;
sg[i] = pnum;
if (i == 2)
sg[i] = 0;
}
for (int j = 1; j <= pnum; ++j) {
if (i * p[j] > n)
break;
vis[i * p[j]] = true;
if (i * p[j] % 2 == 1)
sg[i * p[j]] = sg[p[j]];
if (i % p[j] == 0)
break;
}
}
}
void solve() {
cin >> n;
int sum = 0;
for (int i = 1; i <= n; ++i) {
int tmp;
cin >> tmp;
sum ^= sg[tmp];
}
cout << (sum == 0 ? "Bob" : "Alice") << endl;
}
int main() {
Getprime();
int T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

1525

被折叠的 条评论
为什么被折叠?



