一些废话
对于前三道没什么好说的,只是那个 卡的实在太久,在赛时的时候,直到最后也没想通哪儿有问题,后来补题才发现,就一个地方,钻进牛角尖了,愣是没想到,太狭隘了,我只想到删除开头的部分或者末尾部分,而忘记了中间随机删除一部分都行。而这个 ,我也是懒得喷,凌晨三点写下的文字(卡常实在太恶心了)


我其实没打算补 ,但是补 的时候顺势瞄到了 ,代码量少的令人兴奋。果不其然,大意了,没想象中简单,但也学到了一点东西
A. Painting the Ribbon
题意
有一个大小为 的数组,有 种数字,每种数字无数多个, 可将这 种数字填入到数组中, 最多可修改 次,若 最后能将该数组统一为一种数字,则 失败,输出 ,否则,输出
思路
对于 来说,最优的填法是 1 2 3 4 . . . (m – 2) (m – 1) m 1 2 3 4 . . . (m – 2) (m – 1) m 1 2 3 . . . 直到填满 个数,同一种数字最多出现 次,则 至少要修改 次,该值与 做个比较即可
代码
#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x & (-x))
#define endl '\n'
typedef long long LL;
#define PLL pair<LL, LL>
const LL MOD = 998244353;
const LL MAX = 1e6 + 100;
const LL INF = 0x3f3f3f3f3f3f3f;
void solve() {
int n, m, k;
cin >> n >> m >> k;
int p = (n + m -1) / m;
p = n - p;
if (k >= p) {
cout << "NO" << endl;
}
else {
cout << "YES" << endl;
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
B. Make It Ugly
题意
可以通过以下操作使其所有元素相同,则将该数组成为美丽数组:
- 若 ,则删除掉
给出一个美丽数组,问最少删除多少个元素,使得该数组不为美丽数组,若无法做到,则输出
思路
- 若给出的美丽数组只有一种元素,则直接输出
- 且最后若是所有元素相同,一定是等于 的
若 ,若最后相同的元素不是 ,那 则永远无法消掉,因为我们只能左右两边相等,消去掉中间的, 左边没有数字,所以 是消不掉的, 的处境与 同理。所以如果他们两不相等,那最后始终有一方是消不掉的,无法成为美丽数组,不,从一开始就不是美丽数组,与题干给出的条件,说一开始一定是美丽的相矛盾。后半部分也顺势证明了,因为 始终消不掉,所以若是美丽数组,则最后相同的元素一定是
- 而中间(除开 )若是有不等于 的数字出现,一定是单个独立出现的
反证法,中间若是有不等于 的数字有两个及两个以上连续出现,那么他首先会被消掉剩余连续的两个,而这两个无论如何都无法被旁边等于 a[1] 的数字消掉,所以不是美丽的,与题干给出的一开始就是美丽的数组相矛盾
- 所以,给出的美丽数组的形式大致是 x x x y x x z x x x x x y x y x x x z x x
只要选取连续 的最短区间长度即可,因为只要删除一段 ,若删除的是中间某段,左右两端点不等于 的数就会相邻,回到刚刚讨论的第三点,成为不美丽数组;若删除的第一段或最后一段,则回到讨论的第二点,成为不美丽数组
代码
#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x & (-x))
#define endl '\n'
typedef long long LL;
#define PLL pair<LL, LL>
const LL MOD = 998244353;
const LL MAX = 1e6 + 100;
const LL INF = 0x3f3f3f3f3f3f3f;
void solve() {
int n;
cin >> n;
vector<int> a(n+ 1, 0);
int lst = 0;
int ans = n + 1;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
if (a[i] != a[1]) {
ans = min(ans, i - lst - 1);
lst = i;
}
}
ans = min(ans, n + 1 - lst - 1);
cout << (ans == n ? -1 : ans) << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
C. Long Multiplication
题意
给出两个长度相同的整数 ,可任意交换 ,使得最后 的结果最大,输出
思路
列个竖式:
对于第 位可以试探性的交换一下,看交换前与交换后哪个与高位的乘积更大
比如,样例一, 和 ,看个位是否交换,看第一个竖式,对于 的 来说,他的贡献其实是 ,对于 的 来说,他的贡献是 ,交不交换对于个位来说都是相同的贡献,所以可以不用看,只用看高位即可,所以,对于第一个竖式,个位的贡献是 。看第二个竖式,对于 的个位的 来说,其贡献是 ,对于 的 来说,其贡献是 ,所以该组合个位的贡献是 ,所以,个位交换更好
这是仅针对于个位来说,讨论第 位是否交换,设最高位为 ,则从 到 位去试探,看交换前后哪个乘积之和更大,只要不相等就立马判断是否交换,然后退出,判断下一位。时间复杂度是 ,,最后大约是 级别
代码
#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x & (-x))
#define endl '\n'
typedef long long LL;
#define PLL pair<LL, LL>
const LL MOD = 998244353;
const LL MAX = 1e6 + 100;
const LL INF = 0x3f3f3f3f3f3f3f;
void solve() {
string s, t;
cin >> s >> t;
int n = s.size();
for (int i = 0; i < n; ++i) {
if (s[i] == t[i]) {
continue;
}
int x = s[i] - '0';
int y = t[i] - '0';
for (int j = 0; j < i; ++j) {
int a = s[j] - '0';
int b = t[j] - '0';
int c = x * b + y * a;
int d = x * a + y * b;
if (c != d) {
if (c < d)
swap(s[i], t[i]);
break;
}
}
}
cout << s << endl;
cout << t << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
官方思路
我们知道,对于两个和一定的数来说,他们的差值越小,乘积越大,证明:
我们让 ,在第一次 时,保证 ,之后的所有都让 即可
官方代码
#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x & (-x))
#define endl '\n'
typedef long long LL;
#define PLL pair<LL, LL>
const LL MOD = 998244353;
const LL MAX = 1e6 + 100;
const LL INF = 0x3f3f3f3f3f3f3f;
void solve() {
string x, y;
cin >> x >> y;
int n = x.size(), f = 0;
for (int i = 0; i < n; ++i) {
if ((x[i] > y[i]) == f) {
swap(x[i], y[i]);
}
f |= (x[i] != y[i]);
}
cout << x << '\n'
<< y << '\n';
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
D. Colored Balls
题意
有 个颜色的球,每种球有 个,各种球之间可以相互组合,共有 种组合,每个组最多有两个球,同一种颜色的球不能在同一组,每种组合的组数定义为最小的满足要求的组数,问, 种组合的组数之和为多少,对 取余
思路
抛开其他的,单纯的从题干入手。对于 来说,在包含他的组合里面如何计算一种组数和?
若其余颜色的个数之和都小于等于 ,则答案为 ; 若其余颜色的个数之和大于 ,则答案为其余颜色的个数之和加上 再除以 ,对吗?不对,前半部分没问题,但是后半部分,若在其余颜色的个数中有大于 的存在呢,也就是说,若所选球类的最大值不是 呢?而这个最大值刚好大于其余颜色的球的个数之和,则答案为这个最大值,所以,一切都是在 为所选球类的最大值的基础上进行的。而怎么确保 为选择的最大值,将数组 从小到大排序,当讨论到 的时候,一定是所选球类的最大值
而现在就分为两部分,一部分为剩余个数小于等于 的方案数,对答案的贡献 = 方案数 ,另一部分为剩余个数大于 的球数和,即选择 的总球数和,对答案的贡献 =,两部分的侧重点不一样,但是都要获取到,我们知道,方案数是可达到 ,所以不可能将其设为状态,只能设置为结果,状态自然而然就出来了
- 状态
:轮到第 种球,选择了 个,( 为不选第 种球,反之则选)的方案数
- 转移
若不选第 种球:
若选第 种球:
- 若其余球数小于等于 :
- 若其余球数大于
对了,我写的时候省去了一维,我用两个滚动数组代替
代码
#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x & (-x))
#define endl '\n'
typedef long long LL;
#define PLL pair<LL, LL>
const LL MOD = 998244353;
const LL MAX = 1e6 + 100;
const LL INF = 0x3f3f3f3f3f3f3f;
int dp[5005][2], ndp[5005][2];
int add(int x, int y) {
x += y;
if (x >= MOD) {
x -= MOD;
}
return x;
}
int mul(int x, int y) {
return x * 1LL * y % MOD;
}
void solve() {
int n;
cin >> n;
vector<int> a(n + 1);
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
sort(a.begin() + 1, a.end());
int ans = 0;
dp[0][0] = 1;
for (int i = 1; i <= n; ++i) {
memset(ndp, 0, sizeof(ndp));
for (int j = 0; j <= 5000; ++j) {
ndp[j][0] = add(dp[j][0], dp[j][1]);
}
for (int j = a[i]; j <= min(2 * a[i], 5000); ++j)
{
ndp[j][1] = add(ndp[j][1], dp[j - a[i]][0]);
ndp[j][1] = add(ndp[j][1], dp[j - a[i]][1]);
ans = add(ans, mul(ndp[j][1], a[i]));
}
for (int j = min(a[i] * 2, 5000) + 1; j <= 5000; ++j) {
ndp[j][1] = add(ndp[j][1], dp[j - a[i]][0]);
ndp[j][1] = add(ndp[j][1], dp[j - a[i]][1]);
ans = add(ans, mul(ndp[j][1], ((j + 1) / 2)));
}
memcpy(dp, ndp, sizeof(dp));
}
cout << ans << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
E. Chain Reaction
题意
有 n 个怪物排成一排,第 i 个怪物的生命值为 a_i,你可以任意对活着的怪物产生攻击,攻击范围为从该怪物起向左右分别扩展,直到遇到死去的怪物或者 a_1, a_n 就停止,该范围内的怪物都减少 k 的生命值,该攻击花费一秒。问,k 从 1 到 max(a_1, a_2, . .. a_n) 的每个 k,杀死所有怪物的最少秒数
思路
我先说,我是怀着及其虔诚的态度写下以下文字的,我尽量阐述清楚,但只能是尽量。因为我刚刚录了个视频讲解,我发现,很难讲清楚,我不知道换个形式会不会好一点
讨论当 k = 1 时,此时攻击任何一个怪兽都是等价的,所有怪兽都会减少 1 生命值,若没有怪兽死去,你仍可以攻击任意一个怪兽,持续此操作,直到有怪兽死亡,而死亡的怪兽会将整个区间划分为更小的,全是活着的怪兽区间,对于一个活着的独立区间,攻击该区间任意一个怪兽都是等价的,都会使该区间的怪兽减小 1 生命值,同样,持续此操作,直到出现新的怪兽死去。通过此操作,发现,攻击怪兽的顺序并不重要,你攻击任意一个区间中的任意一个,对于这个区间来说都是等价的,你先攻击哪个区间,对于最后的答案来说也是没有影响的
既然攻击怪兽的顺序对答案没有影响,那么我们就按从左到右一依次攻击。先攻击第一个怪兽,花费 a_i 秒。攻击第二个怪兽,若第二个怪兽的生命值小于等于第一个,那他就免费了,无需花费额外的秒数,若第二个怪兽的生命值大于第一个,则花费 a_2 – a_1 秒。攻击第三个怪兽,若第三个怪兽的生命值小于等于第二个,那他也免费了,若是大于的话,则会额外花费 a_3 – a_2 的秒数,无论第一个怪兽的生命值是多少:若是第一种情况,则在第二个怪兽死去的时候就停止向右传播了,第三个怪兽只能免费 a_2 秒;若是第二种情况,则在第一个怪兽死去的时候,第三个怪兽会免费 a_1 秒,在第二个怪兽死去的时候,第三个怪兽又会免费 a_2 – a_1 秒,整体看来,在第二个怪兽死去后,对于第三个怪兽来说,都会免费 a_2 秒,则额外花费 a_3 – a_2 秒,那第三个怪兽的处境等同于刚刚第二个怪兽的处境,归纳得出:
以上是针对于 k = 1 的情况,而 k 等于其余数也是类似,当 a_{i-1} < a_i 时,对于 a_{i-1} 来说,要让他死亡需花费 秒,而要让 a_i 死亡,需花费 秒,a_{i-1} 可以让 a_i 免费 秒,所以,对于 a_i 来说,他的花费是 秒。而当 a_{i-1} \geq a_i 时,a_i 仍是可以免费的,所以,对于 k 更一般的情况:
现在的问题是,若我们固定 ,去扫描一遍数组 ,时间复杂度是 ,而 ,整体时间复杂度就是 。若我们固定 ,先获取到他的系数 ,时间复杂度是 ,然后去遍历 ,将 加上其对应的 ,时间复杂度是 ,而 的范围是 ,时间复杂度也是 。
我不希望你把它看成是一种算法,这更像是一种通过找规律总结出来的解题技巧
举个栗子:
这里的 “/” 都指的是向上取整
k = 1, 20 / 1 = 20
k = 2, 20 / 2 = 10
k = 3, 20 / 3 = 7
k = 4, 20 / 4 = 5
k = 5, 20 / 5 = 4
k = 6, 20 / 6 = 4
k = 7, 20 / 7 = 3
k = 8, 20 / 8 = 3
k = 9, 20 / 9 = 3
k = 10, 20 / 10 = 2
k = 11, 20 / 11 = 2
k = 12, 20 / 12 = 2
k = 13, 20 / 13 = 2
k = 14, 20 / 14 = 2
k = 15, 20 / 15 = 2
k = 16, 20 / 16 = 2
k = 17, 20 / 17 = 2
k = 18, 20 / 18 = 2
k = 19, 20 / 19 = 2
k = 20, 20 / 20 = 1
k = 21, 20 / 21 = 1
k = 22, 20 / 22 = 1
......
k = mx, 20 / mx = 1
我们发现一个规律:
确定 , 可推出 的范围,用差分将该范围整体加上 ,最后做一个前缀和即为答案
首先,令 ,
总结归纳:
为什么?
我那有限的数学功底不允许我来解释为什么,我只能通过找规律找出该结论。所以时间复杂度我也不大会算,但是你如果想知道的话,去找整除分块的时间复杂度的算法,这个和那个是等价的,其实这个你也可以看成是当 计算的次数取决于 ,当 ,计算的次数取决于 ,所以,整体的时间复杂度是 ,算一次 是这么多, 个 的时间复杂度是 。呃,我怎么说完了,好吧好吧,我还是建议你去搜一下
代码
#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x & (-x))
#define endl '\n'
typedef long long LL;
#define PLL pair<LL, LL>
const LL MOD = 998244353;
const LL MAX = 1e5 + 100;
const LL INF = 0x3f3f3f3f3f3f3f;
void solve() {
LL n;
cin >> n;
vector<LL> a(n + 1, 0);
LL mx = 0;
for (LL i = 1; i <= n; ++i) {
cin >> a[i];
mx = max(mx, a[i]);
}
vector<LL> ans(MAX, 0);
auto divup = [](LL x, LL y)
{
return (x + y - 1) / y;
};
auto work = [&](LL a, LL coef)
{
LL r = a;
ans[r] += coef;
--r;
while (r > 0) {
LL val = divup(a, r);
LL l = divup(a, val);
ans[l] += coef * val;
ans[r + 1] -= coef * val;
r = l - 1;
}
};
for (LL i = 1; i <= n; ++i) {
if (a[i] <= a[i - 1])
continue;
LL v = a[i - 1];
LL u = a[i];
work(v, -1);
work(u, 1);
}
ans[0] = 0;
for (LL i = 1; i <= mx; ++i) {
ans[i] += ans[i - 1];
cout << ans[i] << " ";
}
cout << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}