Codeforces Round 939 (Div. 2) A~E

A.Nene’s Game(循环)

题意:

妮妮发明了一种基于递增序列 a 1 , a 2 , … , a k a_1,a_2,\ldots,a_k a1,a2,,ak的新游戏。

在这个游戏中,最初 n n n个玩家排成一排。在这个游戏的每一轮中,都会发生以下情况:

  • 妮妮发现第 a 1 a_1 a1 a 2 a_2 a2 … \ldots a k a_k ak的玩家排成一排。他们会同时被踢出游戏。如果一排中的 i i i个玩家应该被踢出游戏,但是一排中的玩家少于 i i i个,那么他们就会被跳过。

一旦在某一轮游戏中没有人被踢出游戏,那么所有仍在游戏中的玩家都将被宣布为赢家。

例如,考虑有 a = [ 3 , 5 ] a=[3,5] a=[3,5]名棋手和 n = 5 n=5 n=5名棋手的游戏。让棋手按最初排好的顺序依次命名为棋手 A A A、棋手 B B B … \ldots 、棋手 E E E。那么

  • 在第一轮比赛之前,棋手的排列顺序为 A B C D E ABCDE ABCDE。妮妮发现第 3 3 3和第 5 5 5的球员在一排。他们在第一轮就被踢出局了。
  • 现在棋手们排成 A B D ABD ABD。妮妮找到了第 3 3 3和第 5 5 5的棋手。第 3 3 3位棋手是棋手 D D D,而且一排中没有第 5 5 5位棋手。因此,第二轮只有棋手 D D D被踢出局。
  • 在第三轮中,没有人被踢出游戏,所以游戏在这一轮后结束。
  • 宣布玩家 A A A B B B获胜。

妮妮还没有决定最初会有多少人参加游戏。妮妮给了你 q q q个整数 n 1 , n 2 , … , n q n_1,n_2,\ldots,n_q n1,n2,,nq ,你应该针对每个 1 ≤ i ≤ q 1\le i\le q 1iq 独立回答下面的问题:

  • 如果最初有 n i n_i ni个玩家参加游戏,有多少人会获胜?

分析:

不断循环,如果要删除的位置小于等于剩余的个数,就可以删去该数。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const LL N = 105;
int a[N];
int k, q;

int main() {
    int t;
    cin >> t;
    while (t--) {
        cin >> k >> q;
        for (int i = 1; i <= k; i++)
            cin >> a[i];
        while (q--) {
            int n;
            cin >> n;
            while (true) {
                int cnt = 0;
                for (int i = 1; i <= k; i++) {
                    if (n >= a[i])
                        cnt++;
                }
                if (cnt == 0)
                    break;
                n -= cnt;
            }
            cout << n << ' ';
        }
        cout << endl;
    }
    return 0;
}

B.Nene and the Card Game(思维)

题意:

你和妮妮正在玩纸牌游戏。玩这个游戏使用一副有 2 n 2n 2n张牌的扑克牌。每张牌上都有一个从 1 1 1 n n n的整数,而 1 1 1 n n n的整数正好出现在 2 2 2张牌上。此外,游戏中还有一张放置纸牌的桌子(最初桌子是空的)。

在游戏开始时,这些 2 n 2n 2n张牌会在你和妮妮之间分配,这样每个玩家都会得到 n n n张牌。

之后,你和妮妮轮流进行 2 n 2n 2n次回合,即从你开始,每人轮流 n n n个回合,每个回合

  • 轮到的玩家从手中的牌中选择一张。让 x x x成为上面的数字。
  • 如果桌面上已经有一张整数为 x x x的牌,则轮到他的玩家会得到 1 1 1点数(否则,他不会得到任何点数)。之后,他将选中的整数为 x x x的牌放在桌上。

请注意,回合是公开进行的:每个玩家在每个时刻都能看到桌面上的所有牌。

妮妮非常聪明,所以她总是以最佳方式选牌,以便在游戏结束时( 2 n 2n 2n轮之后)最大化自己的分数。如果她有几种最佳走法,她会选择在游戏结束时使你的得分最小的走法。

更正式地说,妮妮总是以最佳方式轮流下棋,以便在对局结束时首先使她的得分最大化,其次使你在对局结束时的得分最小化。

假设纸牌已经分发完毕,而你手中的纸牌上写有整数 a 1 , a 2 , … , a n a_1,a_2,\ldots,a_n a1,a2,,an,那么你以最优方式轮流出牌所能得到的最大分数是多少?

分析:

最优策略显然是先出自己手上同个点数有两张的牌,如果对方出了双方都有的点数的牌,跟着出。

这样考虑,最优会变成我们先出手上同个点数有两张的牌,对方也这么出,由于这种牌的个数双方是相同的,轮到我们出牌,对方跟着出相同点数。

最后得分是同个点数有两张的个数。

代码:

#include<bits/stdc++.h>

using namespace std;

int main() {
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        vector<int> a(n);
        for (int i = 0; i < n; i++) {
            int x;
            cin >> x;
            x--;
            a[x]++;
        }
        int ans = 0;
        for (int i = 0; i < n; i++) {
            if (a[i] == 2)
                ans++;
        }
        cout << ans << endl;
    }
    return 0;
}

C.Nene’s Magical Matrix(构造)

题意:

魔法少女妮妮有一个 n × n n\times n n×n矩阵 a a a,矩阵中充满了零。矩阵 a a a i i i行的第 j j j个元素表示为 a i , j a_{i,j} ai,j

她可以对这个矩阵进行以下两种类型的运算:

  • 类型 1 1 1操作:在 1 1 1 n n n之间选择一个整数 i i i以及从 1 1 1 n n n的整数排列 p 1 , p 2 , … , p n p_1,p_2,\ldots,p_n p1,p2,,pn。同时为所有 1 ≤ j ≤ n 1\le j\le n 1jn指定 a i , j : = p j a_{i,j}:=p_j ai,j:=pj
  • 类型 2 2 2操作:在 1 1 1 n n n之间选择一个整数 i i i,并从 1 1 1 n n n之间选择一个整数的排列 p 1 , p 2 , … , p n p_1,p_2,\ldots,p_n p1,p2,,pn。同时为所有 1 ≤ j ≤ n 1\le j\le n 1jn指定 a j , i : = p j a_{j,i}:=p_j aj,i:=pj

妮妮想要最大化矩阵 ∑ i = 1 n ∑ j = 1 n a i , j \sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}a_{i,j} i=1nj=1nai,j中所有数字的和。她要求你找出使这个和最大化的运算方法。由于她不希望进行过多的运算,你应提供一个运算次数不超过 2 n 2n 2n的解决方案。

长度为 n n n的排列是由 n n n个不同的整数组成的数组,这些整数从 1 1 1 n n n按任意顺序排列。例如, [ 2 , 3 , 1 , 5 , 4 ] [2,3,1,5,4] [2,3,1,5,4]是一个排列,但 [ 1 , 2 , 2 ] [1,2,2] [1,2,2]不是一个排列( 2 2 2在数组中出现了两次), [ 1 , 3 , 4 ] [1,3,4] [1,3,4]也不是一个排列( n = 3 n=3 n=3,但数组中有 4 4 4)。

分析:

手推一下 n = 3 n=3 n=3的例子,不难发现最大总和的情况是是依次给 i i i行、列赋值 1 , 2 , 3 , … , n 1,2,3,\dots,n 1,2,3,,n。这一结论可以通过归纳法证明。
然后按照结论构造矩阵即可。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;

int main() {
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        LL s = 0;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                s += max(i, j);
            }
        }
        cout << s << ' ' << 2 * n << endl;
        for (int i = n; i >= 1; i--) {
            cout << 1 << ' ' << i;
            for (int j = 1; j <= n; j++) {
                cout << ' ' << j;
            }
            cout << endl;
            cout << 2 << ' ' << i;
            for (int j = 1; j <= n; j++) {
                cout << ' ' << j;
            }
            cout << endl;
        }
    }
    return 0;
}

D.Nene and the Mex Operator(状压)

题意:

妮妮给了你一个长度为 n n n的整数数组 a 1 , a 2 , … , a n a_1,a_2,\ldots,a_n a1,a2,,an

你可以执行以下操作不超过 5 ⋅ 1 0 5 5\cdot 10^5 5105次(可能为零):

  • 选择 l l l r r r这样的两个整数 1 ≤ l ≤ r ≤ n 1\le l\le r\le n 1lrn,计算 x x x MEX ⁡ ( { a l , a l + 1 , … , a r } ) \operatorname{MEX}(\{a_l,a_{l+1},\ldots,a_r\}) MEX({al,al+1,,ar}),同时设置 a l : = x , a l + 1 : = x , … , a r : = x a_l:=x,a_{l+1}:=x,\ldots,a_r:=x al:=x,al+1:=x,,ar:=x

这里,整数集合 { c 1 , c 2 , … , c k } \{c_1,c_2,\ldots,c_k\} {c1,c2,,ck}中的 MEX ⁡ \operatorname{MEX} MEX被定义为在集合 c c c中不出现的最小非负整数 m m m

你的目标是最大化数组 a a a中元素的和。找出最大和,并构建一个操作序列来实现这个和。需要注意的是,你不需要最小化这个序列中的操作次数,你只需要在解决方案中使用不超过 5 ⋅ 1 0 5 5\cdot 10^5 5105的操作。

分析:

对于一段 [ l , r ] [l,r] [l,r],我们要么不变,要么可以通过操作把 [ l , r ] [l,r] [l,r]段内的 a [ i ] a[i] a[i]全部变成 r − l + 1 r-l+1 rl+1

可以先把 [ l , r ] [l,r] [l,r]全部变成 0 0 0,再把 [ l , r ] [l,r] [l,r]变成 l − r , l − r − 1 , l − r − 2 … 0 {l-r,l-r-1,l-r-2\dots 0} lr,lr1,lr20,使用区间 [ l , r ] [l,r] [l,r],最后一步变成 r − l + 1 r-l+1 rl+1

l − r l-r lr可以构造 l − r − 1 , l − r − 2 , … 0 {l-r-1,l-r-2,\dots 0} lr1,lr2,0,使用区间 [ l , r − 1 ] [l,r-1] [l,r1],先变成 r − l + 1 r-l+1 rl+1,再将 [ l + 1 , r − 1 ] [l+1,r-1] [l+1,r1]归零。

使用 D P DP DP标记路径即可。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const LL mod = 1000000007;
vector<int> a;
vector<pair<int, int>> ans;

void deal(int l, int r) {
    set<int> s(a.begin() + l, a.begin() + r + 1);
    LL mex = 0;
    while (s.count(mex))
        mex += 1;
    for (int i = l; i <= r; i++) {
        a[i] = mex;
    }
    ans.push_back({l, r});
}

void solve(int l, int r) {
    if (l == r) {
        if (a[r] != 0) {
            deal(l, r);
        }
        return;
    }
    int len = r - l + 1;
    int target = len - 1;
    if (a[l] != target) {
        if (a[l] > target)
            deal(l, l);
        solve(l + 1, r);
        deal(l, r);
    }
    solve(l + 1, r);
}

int main() {
    int n;
    cin >> n;
    a.resize(n + 1);
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    int dp[20]{}, pre[20]{};
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j < i; j++) {
            int len = i - j;
            if (dp[j] + len * len > dp[i]) {
                dp[i] = dp[j] + len * len;
                pre[i] = j;
            }
        }
        if (dp[i - 1] + a[i] > dp[i]) {
            dp[i] = dp[i - 1] + a[i];
            pre[i] = i - 1;
        }
    }
    int pos = n;
    while (pos != 0) {
        int l = pre[pos] + 1;
        if (l != pos || a[l] == 0) {
            solve(l, pos);
            deal(l, pos);
        }
        pos = pre[pos];
    }
    cout << dp[n] << ' ' << ans.size() << endl;
    for (auto [l, r]: ans) {
        cout << l << ' ' << r << endl;
    }
    return 0;
}

E.Nene vs. Monsters(思维)

题意:

妮妮正在与位于一个圆圈中的 n n n个怪物战斗。这些怪物的编号从 1 1 1 n n n,第 i i i( 1 ≤ i ≤ n 1\le i\le n 1in)个怪物当前的能量水平是 a i a_i ai

由于怪物太强大,妮妮决定使用 "攻击你的邻居"法术与它们战斗。当妮妮使用这个咒语时,以下行动会按以下顺序逐一发生:

  • 1 1 1个怪物攻击第 2 2 2个怪物;
  • 2 2 2只怪物攻击第 3 3 3只怪物;
  • … \ldots
  • ( n − 1 ) (n-1) (n1)只怪物攻击第 n n n只怪物
  • n n n只怪物攻击第 1 1 1只怪物

当能量等级为 x x x的怪物攻击能量等级为 y y y的怪物时,防守怪物的能量等级变为 max ⁡ ( 0 , y − x ) \max(0,y-x) max(0,yx)(攻击怪物的能量等级仍然等于 x x x)。

妮妮会使用这个咒语 1 0 100 10^{100} 10100次,并对付那些自己的能量值仍然不为零的怪物。她想让你确定,一旦她使用所述咒语 1 0 100 10^{100} 10100次,哪些怪物的能量值将不会为零。

分析:

如果连续三个怪物的能量值都是 0 , x , y ( x , y > 0 ) 0,x,y (x,y\gt 0) 0,x,y(x,y>0),那么能量值为 y y y的怪物将最后"死亡"(能量值为 0 0 0)。

如果连续四只怪物的能量等级为 x , y , z , w ( x , y , z , w > 0 ) x,y,z,w (x,y,z,w\gt 0) x,y,z,w(x,y,z,w>0),并且它们在 t t t轮法术后没有死亡,那么 y y y将受到至少 t t t点伤害, z z z将受到至少 ( t − 1 ) + ( t − 2 ) + ⋯ = O ( t 2 ) (t-1)+(t-2)+\cdots=O(t^2) (t1)+(t2)+=O(t2)点伤害, w w w将受到至少 O ( t 3 ) O(t^3) O(t3)点伤害。

也就是说,让 V = max ⁡ i = 1 n a i V=\max_{i=1}^n a_i V=maxi=1nai,在 O ( V 3 ) O(\sqrt[3]{V}) O(3V )个回合后, x , y , z , w x,y,z,w x,y,z,w中至少会有一人死亡。

因此,我们可以暴力模拟这个过程,直到没有四个连续存活的怪物为止。

如果连续四个怪物的能量值都是 0 , x , y , z ( x , y , z > 0 ) 0,x,y,z (x,y,z\gt 0) 0,x,y,z(x,y,z>0) x x x将继续存活, y y y将最后死亡,并在此之前向 z z z造成 D = ( y − x ) + ( y − 2 x ) + ⋯ + ( y   m o d   x ) D=(y-x)+(y-2x)+\cdots+(y\bmod x) D=(yx)+(y2x)++(ymodx)伤害。因此,只有在 z > D z\gt D z>D的情况下, z z z才会存活。

Tips:事实上,我们可以证明在 O ( V k ) O(\sqrt[k]{V}) O(kV )个回合之后,不会有 k k k个连续存活的怪物。让 k k k 3 3 3大,可以进一步降低时间复杂度,但实施和优化的难度会更大,对实际性能影响不大。

代码:

#include<bits/stdc++.h>

using namespace std;
typedef long long LL;
const int MAXN = 2e5 + 5;
int n, a[MAXN];
bool b[MAXN];

bool check() {
    a[n + 1] = a[1], a[n + 2] = a[2], a[n + 3] = a[3];
    for (int i = 1; i <= n; i++)
        if (a[i] && a[i + 1] && a[i + 2] && a[i + 3])
            return true;
    return false;
}

void solve() {
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    if (n == 2) {
        while (a[1] && a[2]) {
            a[2] = max(0, a[2] - a[1]);
            a[1] = max(0, a[1] - a[2]);
        }
        b[1] = (a[1] > 0);
        b[2] = (a[2] > 0);
    } else if (n == 3) {
        while (a[1] && a[2] && a[3]) {
            a[2] = max(0, a[2] - a[1]);
            a[3] = max(0, a[3] - a[2]);
            a[1] = max(0, a[1] - a[3]);
        }
        b[1] = (!a[3] && a[1]);
        b[2] = (!a[1] && a[2]);
        b[3] = (!a[2] && a[3]);
    } else {
        while (check()) {
            for (int i = 1; i <= n; i++)
                a[i % n + 1] = max(0, a[i % n + 1] - a[i]);
        }
        for (int i = 1; i <= n; i++)
            b[i] = false;
        auto attack = [&](LL x, LL y) {
            LL k = x / y;
            return (2 * x - (k + 1) * y) * k / 2;
        };
        for (int p = 1; p <= n; p++)
            if (a[p] && a[p % n + 1])
                a[p % n + 1] = max(0, a[p % n + 1] - a[p]);
            else
                break;
        for (int i = 1; i <= n; i++)
            if (!a[i] && a[i % n + 1]) {
                b[i % n + 1] = true;
                b[(i + 2) % n + 1] = (a[(i + 2) % n + 1] > attack(a[(i + 1) % n + 1], a[i % n + 1]));
            }
    }
    int cnt = 0;
    for (int i = 0; i <= n; i++)
        if (b[i])
            cnt++;
    cout << cnt << endl;
    for (int i = 1; i <= n; i++)
        if (b[i])
            cout << i << ' ';
    cout << endl;
}

int main() {
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

赛后交流

在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。

群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值