A.Nene’s Game(循环)
题意:
妮妮发明了一种基于递增序列a1,a2,…,aka_1,a_2,\ldots,a_ka1,a2,…,ak的新游戏。
在这个游戏中,最初nnn个玩家排成一排。在这个游戏的每一轮中,都会发生以下情况:
- 妮妮发现第a1a_1a1、a2a_2a2、…\ldots…、aka_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被踢出局。
- 在第三轮中,没有人被踢出游戏,所以游戏在这一轮后结束。
- 宣布玩家AAA和BBB获胜。
妮妮还没有决定最初会有多少人参加游戏。妮妮给了你qqq个整数n1,n2,…,nqn_1,n_2,\ldots,n_qn1,n2,…,nq ,你应该针对每个 1≤i≤q1\le i\le q1≤i≤q 独立回答下面的问题:
- 如果最初有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张牌的扑克牌。每张牌上都有一个从111到nnn的整数,而111到nnn的整数正好出现在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,矩阵中充满了零。矩阵aaa第iii行的第jjj个元素表示为ai,ja_{i,j}ai,j。
她可以对这个矩阵进行以下两种类型的运算:
- 类型111操作:在111和nnn之间选择一个整数iii以及从111到nnn的整数排列p1,p2,…,pnp_1,p_2,\ldots,p_np1,p2,…,pn。同时为所有1≤j≤n1\le j\le n1≤j≤n指定ai,j:=pja_{i,j}:=p_jai,j:=pj。
- 类型222操作:在111和nnn之间选择一个整数iii,并从111到nnn之间选择一个整数的排列p1,p2,…,pnp_1,p_2,\ldots,p_np1,p2,…,pn。同时为所有1≤j≤n1\le j\le n1≤j≤n指定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=1∑nj=1∑nai,j中所有数字的和。她要求你找出使这个和最大化的运算方法。由于她不希望进行过多的运算,你应提供一个运算次数不超过2n2n2n的解决方案。
长度为nnn的排列是由nnn个不同的整数组成的数组,这些整数从111到nnn按任意顺序排列。例如,[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^55⋅105次(可能为零):
- 选择lll和rrr这样的两个整数1≤l≤r≤n1\le l\le r\le n1≤l≤r≤n,计算xxx为MEX({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^55⋅105的操作。
分析:
对于一段[l,r][l,r][l,r],我们要么不变,要么可以通过操作把[l,r][l,r][l,r]段内的a[i]a[i]a[i]全部变成r−l+1r-l+1r−l+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}l−r,l−r−1,l−r−2…0,使用区间[l,r][l,r][l,r],最后一步变成r−l+1r-l+1r−l+1。
l−rl-rl−r可以构造l−r−1,l−r−2,…0{l-r-1,l-r-2,\dots 0}l−r−1,l−r−2,…0,使用区间[l,r−1][l,r-1][l,r−1],先变成r−l+1r-l+1r−l+1,再将[l+1,r−1][l+1,r-1][l+1,r−1]归零。
使用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个怪物战斗。这些怪物的编号从111到nnn,第iii(1≤i≤n1\le i\le n1≤i≤n)个怪物当前的能量水平是aia_iai。
由于怪物太强大,妮妮决定使用 "攻击你的邻居"法术与它们战斗。当妮妮使用这个咒语时,以下行动会按以下顺序逐一发生:
- 第111个怪物攻击第222个怪物;
- 第222只怪物攻击第333只怪物;
- …\ldots…
- 第(n−1)(n-1)(n−1)只怪物攻击第nnn只怪物
- 第nnn只怪物攻击第111只怪物
当能量等级为xxx的怪物攻击能量等级为yyy的怪物时,防守怪物的能量等级变为max(0,y−x)\max(0,y-x)max(0,y−x)(攻击怪物的能量等级仍然等于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)(t−1)+(t−2)+⋯=O(t2)点伤害,www将受到至少O(t3)O(t^3)O(t3)点伤害。
也就是说,让V=maxi=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=(y−x)+(y−2x)+⋯+(ymodx)伤害。因此,只有在z>Dz\gt Dz>D的情况下,zzz才会存活。
Tips:事实上,我们可以证明在O(Vk)O(\sqrt[k]{V})O(kV)个回合之后,不会有kkk个连续存活的怪物。让kkk比333大,可以进一步降低时间复杂度,但实施和优化的难度会更大,对实际性能影响不大。
代码:
#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,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。