Educational Codeforces Round 169 (Div. 2) A~E

A.Closest Point(思维)

题意:

考虑直线上的一组点。两点 i i i j j j之间的距离为 ∣ i − j ∣ |i-j| ij

如果集合中没有其他点 k k k使得 j j j k k k的距离严格小于 j j j i i i的距离,则集合中的点 i i i与集合中的点 j j j的距离最近。换句话说,集合中所有其他点到 j j j的距离都大于或等于 ∣ i − j ∣ |i-j| ij

例如,考虑一组点 { 1 , 3 , 5 , 8 } \{1,3,5,8\} {1,3,5,8}

  • 对于点 1 1 1,最近的点是 3 3 3(其他点的距离大于 ∣ 1 − 3 ∣ = 2 |1-3|=2 ∣13∣=2);
  • 对于点 3 3 3来说,有两个最近点: 1 1 1 5 5 5
  • 对于点 5 5 5,最近的点是 3 3 3(而不是 8 8 8)。(但不是 8 8 8,因为它的距离大于 ∣ 3 − 5 ∣ |3-5| ∣35∣);
  • 对于点 8 8 8,最近的点是 5 5 5

给你一组点。你必须在这个集合加入一个整数点,使它与集合中现有的每一个点都不同,并使它成为离集合中每一个点最近的点。这可能吗?

分析:

模拟题意可以发现:

  • n > 2 n>2 n>2时,恒不成立
  • n = 2 n=2 n=2时,若两个数不相邻,则成立
  • n = 1 n=1 n=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(思维)

题意:

一排有 100 100 100个房间,房间之间有 99 99 99扇门;第 i i i扇门连接着 i i i i + 1 i+1 i+1两个房间。每扇门既可以上锁,也可以不上锁。最初,所有的门都没有上锁。

如果房间 y y y和房间 x x x之间的所有门都没有上锁,我们就说房间 y y y可以从房间 x x x到达 y y y

你知道吗?

  • 爱丽丝在 [ l , r ] [l,r] [l,r]的某个房间里;
  • 鲍勃位于 [ L , R ] [L,R] [L,R]中的某个房间;
  • 爱丽丝和鲍勃在不同的房间。

但是,你并不知道他们具体在哪个房间。

你不想让爱丽丝和鲍勃接触到对方,所以你要锁上一些门来防止他们接触到对方。无论爱丽丝和鲍勃在给定段落中的起始位置如何,要使他们不能相遇,你必须锁上的门的最小数目是多少?

分析:

本题我们仍然分情况讨论:

  • 两人没有相交的区域,那么只需要关闭一扇门即可;
  • 两人完全重合,那么这些门需要全部关闭,即 r − l r−l rl
  • 左边界或者右边界有重合,以右边界重合为例,找出他们两左边界的最大值 a n s = r − m a x ( l , L ) + 1 ans=r−max(l,L)+1 ans=rmax(l,L)+1,首先 r r r m a x ( l , L ) max(l,L) max(l,L)的门必须关闭,那么假设 l < L l<L l<L,对 L L L L − 1 L-1 L1 L L L,两人会有交界,这个门也需要关闭);
  • 两人相交无重合, a n s = m i n ( r , R ) − m a x ( l , L ) + 2 ans=min(r,R)−max(l,L)+2 ans=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(枚举)

题意:

爱丽丝和鲍勃有 n n n件物品想平分,于是他们决定玩一个游戏。所有物品都有成本,第 i i i件物品的成本为 a i a_i ai。玩家从爱丽丝开始轮流移动。

在每个回合中,玩家从剩下的物品中选择一个并拿走。游戏一直进行到没有物品为止。

假设 A A A是爱丽丝拿走物品的总费用, B B B是鲍勃拿走物品的总费用。这样,游戏的得分就等于 A − B A-B AB

爱丽丝希望得分最大化,而鲍勃希望得分最小化。爱丽丝和鲍勃都将以最优方式进行博弈。

但游戏将在明天进行,所以今天鲍勃可以稍微修改一下成本。他可以将几个(可能一个也没有,也可能全部)项目的成本 a i a_i ai增加一个整数值(可能每个项目增加相同的值,也可能增加不同的值)。但是,增加的总金额必须小于或等于 k k k。否则,爱丽丝可能会怀疑。请注意,鲍勃不能减少成本,只能增加成本。

鲍勃能得到的最低分数是多少?

分析:

很显然从大到小排序则若为第偶数个元素,归鲍勃,否则归爱丽丝。显然鲍勃只能让自己的元素的值和在它前面一个位置的元素的值尽量的接近的时候最优。因此直接暴力枚举鲍勃的每一个元素计算其可以最多加多少,贪心计算即可。

代码:

#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(思维)

题意:

直线上有 n n n座城市。这些城市的编号从 1 1 1 n n n

传送门用于在城市之间移动。传送门有 4 4 4种颜色:蓝色、绿色、红色和黄色。每个城市都有两种不同颜色的传送门。如果城市 i i i和城市 j j j的传送门颜色相同,你就可以在这两个城市之间移动(例如,你可以在"蓝-红"城市和"蓝-绿"城市之间移动)。这种移动需要花费 ∣ i − j ∣ |i-j| ij枚金币。

你的任务是回答 q q q个独立的问题:计算从城市 x x x移动到城市 y y y的最小花费。

分析:

本题我们考虑分类讨论,发现在大多数情况下给定的询问 x , y x,y x,y均可以直达,此时的代价即为 ∣ x − y ∣ |x−y| xy

无法直达的情况 x , y x,y x,y所在城市的种类只有三种:BGRYBRGYBYGR。容易发现对于这三种情况,仅需找到任意一个类型与它们均不同的城市 p p p,就可以作为中转点使得 x , y x,y x,y可互相到达,代价即为 ∣ x − p ∣ + ∣ y − p ∣ |x−p|+|y−p| xp+yp

若中转城市恰好位于 [ x , y ] [x,y] [x,y]中则代价即为 ∣ x − y ∣ |x−y| xy,否则仅需检查距离 x , y x,y x,y最近的中转城市即可。于是考虑按照位置升序维护每种城市的位置,对于每次询问先判断 x , y x,y x,y是否可以直达,若无法直达则枚举中转城市的类型,检查 [ x + 1 , y − 1 ] [x+1,y−1] [x+1,y1]中有无中转城市,若没有则在 [ 1 , x − 1 ] , [ y + 1 , n ] [1,x−1],[y+1,n] [1,x1],[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(博弈)

题意:

爱丽丝和鲍勃正在玩一个游戏。他们有 n n n堆棋子,其中第 i i i堆最初包含 a i a_i ai个棋子。

在他们的回合中,玩家可以选择任意一堆石子,并从中取出任意正数的石子,但有一个条件:

  • 让该堆棋子的当前数量为 x x x。不允许从石堆中取出 y y y个数的石子,使得 x x x y y y的最大公约数不等于 1 1 1

无法下棋的棋手输棋。两位棋手都以最佳状态下棋(也就是说,如果一位棋手的策略能让他获胜,那么无论对手如何回应,他都会获胜)。爱丽丝先下。

确定谁会赢。

分析:

读题发现就是标准 N i m Nim Nim游戏,考虑如何求 S G SG SG函数。

简单模拟一下可以发现,对于单堆石子当且仅当个数 x x x为奇数时先手必胜,此时仅需取走 x − 2 x−2 x2个石子即可。然后考虑通过对 1 ∼ a i 1∼a_i 1ai S G SG SG函数拓展到多堆石子即可。

因为互质对的数量级很大,直接枚举转移求 S G SG SG函数显然会超时。观察数据范围发现线性筛似乎可以跑,考虑寻找每个值与其最小质因子的关系。推导发现:

  • S G ( 0 ) = 0 SG(0)=0 SG(0)=0
  • S G ( 1 ) = m e x 0 = 1 SG(1)=mex{0}=1 SG(1)=mex0=1
  • 对于所有偶数 x x x S G ( x ) = 0 SG(x)=0 SG(x)=0
  • 对于所有大于 2 2 2的质数 p p p,对任意 1 ≤ i < p 1\le i\lt p 1i<p g c d ( p , i ) = 1 gcd(p,i)=1 gcd(p,i)=1,则 S G ( p ) = m e x [ 1 , 2 , ⋯ , p − 1 ] = r a n k ( p ) SG(p)=mex[1,2,⋯,p−1]=rank(p) SG(p)=mex[1,2,,p1]=rank(p) r a n k ( p ) rank(p) rank(p)表示 p p p在所有质数(包括 2 2 2)中的排名。
  • 对于所有奇数 x x x,设其最小质因子为 p p p,发现有 g c d ( x , x − p ) ≠ 0 gcd(x,x−p)≠0 gcd(x,xp)=0,且对任意 1 ≤ y < p 1\le y\lt p 1y<p g c d ( i , x ) = 1 gcd(i,x)=1 gcd(i,x)=1。则 x x x第一个不能转移到的位置是 p p p。通过观察转移方式可以发现 S G ( x ) = S G ( p ) SG(x)=SG(p) SG(x)=SG(p)

由此使用线性筛,对于每个数求得其最小质因子,即可求得所有 S G SG SG函数,则仅需判断 S G ( a 1 ) ⊕ ⋯ ⊕ S G ( a n ) ≠ 0 SG(a_1)⊕⋯⊕SG(a_n)≠0 SG(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,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

  • 30
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值