A. Split the Multiset (思维)
题意:
有一个多集 S S S 。最初,多集仅包含一个正整数 n n n 。即 S = { n } S=\{n\} S={n} 。此外,还有一个给定的正整数 k k k 。
在一个操作中,您可以选择 S S S 中的任意正整数 u u u ,并从 S S S 中删除一个 u u u 的副本。然后,将不超过 k k k 个正整数插入 S S S ,以便所有插入的整数之和等于 u u u 。
找出使 S S S 包含 n n n 个 1 的最少操作数。
分析:
将题目转化为需要将 1 1 1个数字分成 n n n个部分,也就是要分出 n − 1 n - 1 n−1个部分,每次可以分出 k − 1 k - 1 k−1个部分。答案为 ( n − 1 ) / ( k − 1 ) (n-1)/(k-1) (n−1)/(k−1)向上取整。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define endl '\n'
#define PII pair<LL, LL>
const int INF = 1e9;
const int mod = 1e9 + 7;
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t = 1;
cin >> t;
while (t--)
{
int n, k;
cin >> n >> k;
cout << ceil((n - 1) / (double)(k - 1)) << endl;
}
return 0;
}
B.Make Majority (思维)
题意:
给定一个序列 [ a 1 , … , a n ] [a_1,\ldots,a_n] [a1,…,an] ,其中每个元素 a i a_i ai 为 0 0 0 或 1 1 1 。现在可以对该序列应用多个(可能为零个)操作。在每个操作中,选择两个整数 1 ≤ l ≤ r ≤ ∣ a ∣ 1\le l\le r\le |a| 1≤l≤r≤∣a∣ (其中 ∣ a ∣ |a| ∣a∣ 是 a a a 的当前长度),并将 [ a l , … , a r ] [a_l,\ldots,a_r] [al,…,ar] 替换为单个元素 x x x ,其中 x x x 是 [ a l , … , a r ] [a_l,\ldots,a_r] [al,…,ar] 的多数。
这里,由 0 0 0 和 1 1 1 组成的序列的多数定义如下:假设序列中分别有 c 0 c_0 c0 个零和 c 1 c_1 c1 个一。
- 如果是 c 0 ≥ c 1 c_0\ge c_1 c0≥c1 ,则多数为 0 0 0 。
- 如果是 c 0 < c 1 c_0 < c_1 c0<c1 ,则多数为 1 1 1 。
例如,假设 a = [ 1 , 0 , 0 , 0 , 1 , 1 ] a=[1,0,0,0,1,1] a=[1,0,0,0,1,1] 。如果我们选择 l = 1 , r = 2 l=1,r=2 l=1,r=2 ,则结果序列将为 [ 0 , 0 , 0 , 1 , 1 ] [0,0,0,1,1] [0,0,0,1,1] 。如果我们选择 l = 4 , r = 6 l=4,r=6 l=4,r=6 ,则结果序列将为 [ 1 , 0 , 0 , 1 ] [1,0,0,1] [1,0,0,1] 。
确定您是否可以通过有限数量的操作来制作 a = [ 1 ] a=[1] a=[1] 。
分析:
我们可以将 a a a中的所有连续的 0 0 0合并成一个 0 0 0,此时我们只需要对比 1 1 1与 0 0 0的数量即可,当 c 1 > c 0 c_1 > c_0 c1>c0 就可以生成 1 {1} 1。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define endl '\n'
#define PII pair<LL, LL>
const int INF = 1e9;
const int mod = 1e9 + 7;
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t = 1;
cin >> t;
while (t--)
{
int n;
cin >> n;
string s;
cin >> s;
int c0 = 0, c1 = 0;
string tmp(s);
tmp += '1';
for (int i = 0; i < n; i++)
{
if (tmp[i] == '1')
{
c1++;
}
else if (tmp[i] == '0' && tmp[i + 1] == '1')
{
c0++;
}
}
if (c1 > c0)
{
cout << "Yes" << endl;
}
else
{
cout << "No" << endl;
}
}
return 0;
}
C.Increasing Sequence with Fixed OR (思维)
题意:
给定一个正整数 n n n 。找出满足以下条件的最长正整数序列 a = [ a 1 , a 2 , … , a k ] a=[a_1,a_2,\ldots,a_k] a=[a1,a2,…,ak] ,并输出该序列:
- 对于所有 1 ≤ i ≤ k 1\le i\le k 1≤i≤k ,结果为 a i ≤ n a_i\le n ai≤n 。
- 对于所有 2 ≤ i ≤ k 2\le i\le k 2≤i≤k ,结果为 a a a ,结果为严格递增。也就是说,对于所有 2 ≤ i ≤ k 2\le i\le k 2≤i≤k ,结果为 a i > a i − 1 a_i > a_{i-1} ai>ai−1 。
- 对于所有 2 ≤ i ≤ k 2\le i\le k 2≤i≤k ,结果为 a i ∣ a i − 1 = n a_i\,|\,a_{i-1}=n ai∣ai−1=n ,其中 ∣ | ∣ 表示
分析:
我们先将 n n n转化为二进制。观察样例后发现,如果 n n n的二进制含有 x x x个 1 1 1,那么序列长度为 x + 1 x+1 x+1。然后考虑如何构造。我们只需要依次去掉低位的 1 1 1,就可以满足以上要求。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define endl '\n'
#define PII pair<LL, LL>
const int INF = 1e9;
const int mod = 1e9 + 7;
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t = 1;
cin >> t;
while (t--)
{
LL n;
cin >> n;
vector<LL> ans;
ans.push_back(n);
for (LL i = 0; i < 63; i++)
{
if (n >> i & 1)
{
LL ins = n - ((LL)1 << i);
if (ins == 0)
continue;
ans.push_back(n - ((LL)1 << i));
}
}
reverse(ans.begin(), ans.end());
int k = ans.size();
cout << k << endl;
for (auto x : ans)
{
cout << x << " ";
}
cout << endl;
}
return 0;
}
D.The Omnipotent Monster Killer(dp)
题意:
在编号为
i
i
i (
1
≤
i
≤
n
1\le i\le n
1≤i≤n ) 的顶点上,有一个攻击点数为
a
i
a_i
ai 的怪物。你想与怪物战斗
1
0
100
10^{100}
10100 轮。
在每个回合中,以下情况按顺序发生:
- 所有活着的怪物都会攻击你。你的生命值会减少所有活着的怪物攻击点的总和。
- 你选择一些(可能是全部或没有)怪物并杀死它们。被杀死后,怪物将无法在将来进行任何攻击。
有一个限制:在一轮中,你不能杀死两个由边直接连接的怪物。
如果你选择最佳攻击的怪物,那么在所有回合之后你的最小生命值减少量是多少?
分析:
我们希望每次砍掉的点权值和尽可能大,可以发现总操作次数不会很多,即使是给树黑白染色,然后砍掉权值较大的部分也可以让总权值减少至少一半。所以最多只用
60
60
60次操作就可以删掉所有的点。
假设一个节点在第
i
i
i个回合删除,那么它的伤害是
i
×
a
i
i \times a_i
i×ai。
f
[
x
]
[
j
]
f[x][j]
f[x][j]表示
x
x
x节点在
j
j
j回合删除,并且以
x
x
x为根的子树的伤害最小值,那么有
f
[
x
]
[
j
]
=
j
×
a
x
+
∑
m
i
n
(
f
[
i
]
[
k
]
)
,
k
!
=
j
f[x][j]=j \times a_x +\sum min(f[i][k]) ,k!=j
f[x][j]=j×ax+∑min(f[i][k]),k!=j。最终答案为
m
i
n
(
f
[
0
]
[
j
]
)
min(f[0][j])
min(f[0][j])。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define endl '\n'
#define PII pair<LL, LL>
const int INF = 1e9;
const int mod = 1e9 + 7;
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t = 1;
cin >> t;
while (t--)
{
LL n;
cin >> n;
vector<LL> a(n);
vector<vector<LL>> g(n, vector<LL>());
vector<vector<LL>> f(n, vector<LL>(21, 0));
for (int i = 0; i < n; i++)
cin >> a[i];
for (int i = 0; i < n - 1; i++)
{
int a, b;
cin >> a >> b;
a--, b--;
g[a].push_back(b), g[b].push_back(a);
}
auto dfs = [&](auto self, int u, int v) -> void
{
for (int i = 1; i <= 20; i++)
{
f[u][i] = i * a[u];
}
for (auto x : g[u])
{
if (x == v)
continue;
self(self, x, u);
for (int i = 1; i <= 20; i++)
{
LL mi = 1e18;
for (int j = 1; j <= 20; j++)
{
if (i == j)
continue;
mi = min(mi, f[x][j]);
}
f[u][i] += mi;
}
}
};
dfs(dfs, 0, -1);
LL ans = 1e18;
for (int i = 1; i <= 20; i++)
{
ans = min(ans, f[0][i]);
}
cout << ans << endl;
}
return 0;
}
E.Range Minimum Sum (数据结构)
题意:
对于长度为 n n n 的数组 [ a 1 , a 2 , … , a n ] [a_1,a_2,\ldots,a_n] [a1,a2,…,an] ,将 f ( a ) f(a) f(a) 定义为所有子段的最小元素之和。即
f ( a ) = ∑ l = 1 n ∑ r = l n min l ≤ i ≤ r a i . f(a)=\sum_{l=1}^n\sum_{r=l}^n \min_{l\le i\le r}a_i. f(a)=l=1∑nr=l∑nl≤i≤rminai.
排列是从 1 1 1 到 n n n 的整数序列,长度为 n n n ,每个数字只包含一次。给定一个排列 [ a 1 , a 2 , … , a n ] [a_1,a_2,\ldots,a_n] [a1,a2,…,an] 。对于每个 i i i ,解决以下问题:
- 从 a a a 中删除 a i a_i ai ,连接剩余部分,得到 b = [ a 1 , a 2 , … , a i − 1 , a i + 1 , … , a n ] b = [a_1,a_2,\ldots,a_{i-1},\;a_{i+1},\ldots,a_{n}] b=[a1,a2,…,ai−1,ai+1,…,an] 。
- 计算 f ( b ) f(b) f(b) 。
分析:
我们先考虑如果不删掉一个元素的操作,可以用 s e t set set进行解决,这些东西可以处理出一个点 u u u左右侧第一个小的位置 l , r l,r l,r。那么所有左端点为 [ l + 1 , u ] [l+1,u] [l+1,u],右端点位于 [ u , r − 1 ] [u,r-1] [u,r−1]的区间都满足最小值为 a u a_u au。我们先计算起始的答案,再考虑删掉一个数 u u u改变了什么。
- 首先所有最小值为 a u a_u au的区间被删掉了,这部分我们计算起始答案的时候就算了,记录一下即可。
- 对于 a i < a u a_i < a_u ai<au的位置,如果 ( i , u ) (i,u) (i,u)之间没有 < a i < a_i <ai的数字,那么最小值为 a i a_i ai的区间也会少掉一些贡献。这个贡献其实是个静态的区间加,可以差分解决。
- 对于 a i > a u a_i > a_u ai>au的位置,因为 a u a_u au被删掉了, a i a_i ai为最小值的区间增加了,这类贡献我们可以在 a i a_i ai处统计。我们可以找到 a i a_i ai后面第一个 < a i < a_i <ai的位置 j j j,那么 a j a_j aj被删掉时的最小值为 a i a_i ai的区间就会增多,我们再找到 a i a_i ai后面第二个 < a i < a_i <ai的位置 k k k,增多的数量就是 ( k − j − 1 ) × ( i − l i ) (k-j-1) \times(i-l_i) (k−j−1)×(i−li)。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define endl '\n'
#define PII pair<LL, LL>
const int INF = 1e9;
const int mod = 1e9 + 7;
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t = 1;
cin >> t;
while (t--)
{
int n;
cin >> n;
vector<int> a(n + 1), p(n + 1);
for (int i = 1; i <= n; i++)
cin >> a[i], p[a[i]] = i;
set<int> s;
s.insert(0);
s.insert(n + 1);
vector<LL> num(n + 1);
LL sum = 0;
vector<LL> d(n + 1);
auto add = [&](int l, int r, LL v)
{
if (l > r)
return;
d[l] += v;
if (r + 1 <= n)
d[r + 1] -= v;
};
for (int i = 1; i <= n; i++)
{
int u = p[i];
int L = *prev(s.lower_bound(u));
int R = *s.upper_bound(u);
num[u] = 1LL * i * (u - L) * (R - u);
sum += num[u];
add(L + 1, u - 1, -1LL * i * (R - u));
add(u + 1, R - 1, -1LL * i * (u - L));
{
auto it = s.upper_bound(u);
if (it != s.end())
{
auto tmp = next(it);
if (tmp != s.end())
{
int p1 = *it, p2 = *tmp;
num[p1] -= 1LL * i * (p2 - p1 - 1) * (u - L);
}
}
}
{
auto it = s.lower_bound(u);
if (it != s.begin())
{
it = prev(it);
if (it != s.begin())
{
auto pre = prev(it);
int p1 = *it, p2 = *pre;
num[p1] -= 1LL * i * (p1 - p2 - 1) * (R - u);
}
}
}
s.insert(u);
}
for (int i = 1; i <= n; i++)
{
d[i] += d[i - 1];
cout << sum - num[i] + d[i] << " ";
}
cout << endl;
}
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。