Codeforces Round 939 (Div. 2) A~E

A.Nene’s Game(循环)

题意:

妮妮发明了一种基于递增序列a1,a2,…,aka_1,a_2,\ldots,a_ka1,a2,,ak的新游戏。

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

  • 妮妮发现第a1a_1a1a2a_2a2…\ldotsaka_kak的玩家排成一排。他们会同时被踢出游戏。如果一排中的iii个玩家应该被踢出游戏,但是一排中的玩家少于iii个,那么他们就会被跳过。

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

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

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

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

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

分析:

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

代码:

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

题意:

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

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

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

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

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

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

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

假设纸牌已经分发完毕,而你手中的纸牌上写有整数a1,a2,…,ana_1,a_2,\ldots,a_na1,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×nn\times nn×n矩阵aaa,矩阵中充满了零。矩阵aaaiii行的第jjj个元素表示为ai,ja_{i,j}ai,j

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

  • 类型111操作:在111nnn之间选择一个整数iii以及从111nnn的整数排列p1,p2,…,pnp_1,p_2,\ldots,p_np1,p2,,pn。同时为所有1≤j≤n1\le j\le n1jn指定ai,j:=pja_{i,j}:=p_jai,j:=pj
  • 类型222操作:在111nnn之间选择一个整数iii,并从111nnn之间选择一个整数的排列p1,p2,…,pnp_1,p_2,\ldots,p_np1,p2,,pn。同时为所有1≤j≤n1\le j\le n1jn指定aj,i:=pja_{j,i}:=p_jaj,i:=pj

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

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

分析:

手推一下n=3n=3n=3的例子,不难发现最大总和的情况是是依次给iii行、列赋值1,2,3,…,n1,2,3,\dots,n1,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(状压)

题意:

妮妮给了你一个长度为nnn的整数数组a1,a2,…,ana_1,a_2,\ldots,a_na1,a2,,an

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

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

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

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

分析:

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

可以先把[l,r][l,r][l,r]全部变成000,再把[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+1r-l+1rl+1

l−rl-rlr可以构造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+1r-l+1rl+1,再将[l+1,r−1][l+1,r-1][l+1,r1]归零。

使用DPDPDP标记路径即可。

代码:

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

题意:

妮妮正在与位于一个圆圈中的nnn个怪物战斗。这些怪物的编号从111nnn,第iii(1≤i≤n1\le i\le n1in)个怪物当前的能量水平是aia_iai

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

  • 111个怪物攻击第222个怪物;
  • 222只怪物攻击第333只怪物;
  • …\ldots
  • (n−1)(n-1)(n1)只怪物攻击第nnn只怪物
  • nnn只怪物攻击第111只怪物

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

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

分析:

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

如果连续四只怪物的能量等级为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),并且它们在ttt轮法术后没有死亡,那么yyy将受到至少ttt点伤害,zzz将受到至少(t−1)+(t−2)+⋯=O(t2)(t-1)+(t-2)+\cdots=O(t^2)(t1)+(t2)+=O(t2)点伤害,www将受到至少O(t3)O(t^3)O(t3)点伤害。

也就是说,让V=max⁡i=1naiV=\max_{i=1}^n a_iV=maxi=1nai,在O(V3)O(\sqrt[3]{V})O(3V)个回合后,x,y,z,wx,y,z,wx,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)xxx将继续存活,yyy将最后死亡,并在此之前向zzz造成D=(y−x)+(y−2x)+⋯+(y mod x)D=(y-x)+(y-2x)+\cdots+(y\bmod x)D=(yx)+(y2x)++(ymodx)伤害。因此,只有在z>Dz\gt Dz>D的情况下,zzz才会存活。

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

代码:

#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、付费专栏及课程。

余额充值