多校 Day 1
A
选择 ,使得 最大。
由于可以不选,可以把 加入到 和 中。
若固定 ,则 ,是关于 的一次函数,因此要取到极值, 一定在两个端点上。固定 的情况同理。
因此全局最优解一定在这之中:
1 |
|
F
把总期望拆成 每盏灯打开时的 连续段的期望个数 之和。
给每盏灯独立分配一个 上的均匀随机数 ,按 从小到大的顺序开灯。由于这些随机数几乎一定两两不同,它们的相对大小构成一个均匀随机排列,与原问题完全等价。
亮灯连续段的数量等于「既是亮着的,又是所在段的起点」的位置数。位置 是起点,需要满足:
- 第 盏灯亮着。
- ,或第 盏灯不亮。
设灯 正在打开,。
1. 端点
第 盏灯一定是起点。位置 因为左边有灯 亮着,不可能是起点。
其余 个位置 成为起点的概率均为 (自身亮且左边不亮)。
积分得
2. 端点
位置 成为起点的概率为 。位置 成为起点的概率为 (左边不亮)。
其余 个位置成为起点的概率为 。
同样得到
3. 内部点
- 位置 :概率 。
- 位置 :正在打开,它成为起点要求左边不亮,概率 。
- 位置 :左边是 已亮,不可能成为起点,概率 。
- 其余 个位置:概率 。
积分得
汇总
记 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
using namespace std;
typedef long long ll;
// #define int long long
const int mod = 998244353;
const int maxn = 2e5 + 5;
int qpow(int a, int n) {
int res = 1;
while (n) {
if (n & 1) res = 1ll * res * a % mod;
a = 1ll * a * a % mod;
n /= 2;
}
return res;
}
int n;
int a[maxn];
void Solve() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
int ans = 0;
if (n == 1) {
cout << a[1] << endl;
return;
}
ans = 1ll * (n + 4) * (a[1] + a[n]) % mod;
for (int i = 2; i < n; i++) {
ans = (ans + 1ll * (n + 3) * (a[i]) % mod) % mod;
}
cout << 1ll * ans * qpow(6, mod - 2) % mod << endl;
}
signed main() {
// freopen("1006.in", "r", stdin);
// freopen("1006.out", "w", stdout);
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int T;
cin >> T;
while (T--) {
Solve();
}
return 0;
}
E
构造 ,字符集设为 。
令 的数量为 , 的数量为 。
全 串对答案的贡献为 ,全 串对答案的贡献为 。
所以只需要找到最大的 ,使得 ,然后再填 个 就行了。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using namespace std;
using i64 = long long;
const int inf = 2e9;
const int maxn = 1e6 + 5;
int k;
void solve() {
cin >> k;
int n = 1;
while (n * (n + 1) / 2 <= k) ++n;
--n;
vector<char> ans;
for (int i = 1; i <= n + 1; i++) ans.push_back('a');
for (int i = 1; i <= k - n * (n + 1) / 2; i++) ans.push_back('b');
cout << ans.size() << " " << 2 << endl;
for (char x : ans) cout << x;
cout << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
J
比较字典序1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
using namespace std;
typedef long long ll;
const int mod = 998244353;
const int maxn = 1e6 + 5;
int n;
int x[maxn], pre[maxn];
void Solve() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> x[i];
}
for (int i = 1; i <= n; i++) pre[i] = pre[i - 1] + x[i];
if (n & 1) {
for (int i = n / 2; i >= 1; i--) {
if (pre[i] > pre[n] - pre[n - i]) {
cout << "YES" << endl;
return;
} else if (pre[i] < pre[n] - pre[n - i]) {
cout << "NO" << endl;
return;
}
}
cout << "NO" << endl;
} else {
for (int i = n / 2; i >= 1; i--) {
if (pre[i] > pre[n] - pre[n - i + 1]) {
cout << "YES" << endl;
return;
} else if (pre[i] < pre[n] - pre[n - i + 1]) {
cout << "NO" << endl;
return;
}
}
cout << "NO" << endl;
}
return;
}
signed main() {
// freopen("1010.in", "r", stdin);
// freopen("1010.out", "w", stdout);
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int T;
cin >> T;
while (T--) {
Solve();
}
return 0;
}
D
考虑一个节点 的两个儿子 和 ,先放哪个收益更高只取决于 和 $\frac{A_y}{B_y}。因此用优先队列维护这个值,每次再用并查集维护所属连通块的权值和即可。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
using namespace std;
typedef long long ll;
const int mod = 998244353;
const int maxn = 2e5 + 5;
struct DSU {
vector<int> f, siz;
int find(int x) {
return f[x] == x ? x : f[x] = find(f[x]);
}
bool merge(int a, int b) {
int p = find(a), q = find(b);
if (p == q) return 0;
f[q] = p;
siz[p] += siz[q];
return 1;
}
DSU(int n) {
f.resize(n + 1);
siz.resize(n + 1);
for (int i = 1; i <= n; i++) {
f[i] = i, siz[i] = 1;
}
}
};
int n;
int a[maxn], b[maxn], fa[maxn];
void Solve() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n;i ++) {
cin >> b[i];
}
for (int i = 1; i <= n; i++) {
cin >> fa[i];
}
DSU dsu(n);
priority_queue<pair<double, int>> q;
vector<int> vis(n + 1, 0);
int ans = 0;
for (int i = 2; i <= n; i++) {
q.push({1.0 * a[i] / b[i], i});
}
while (!q.empty()) {
int now = (q.top()).second; q.pop();
if (vis[now]) continue;
vis[now] = 1;
int nowf = dsu.find(fa[now]);
now = dsu.find(now);
ans += a[now] * b[nowf];
dsu.merge(nowf, now);
a[nowf] += a[now], b[nowf] += b[now];
if (nowf != 1) q.push({1.0 * a[nowf] / b[nowf], nowf});
}
cout << ans << endl;
}
signed main() {
// freopen("1004.in", "r", stdin);
// freopen("1004.out", "w", stdout);
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int T;
cin >> T;
while (T--) {
Solve();
}
return 0;
}
H
首先,最后答案中,相邻数字串的长度差一定不超过 1。
考虑答案中最长数字串的长度,会发现其不会超过 。
设 表示前 位中,以 结尾的数字子序列长度为 时的最长上升数字子序列长度。
我们考虑按 进行 dp,分两种情况讨论:
- 当 时,我们直接维护一个 表示前 位中,最后一个数字子序列长度不超过 时的最长上升数字子序列长度,每次就可以 转移;
- 当 ,注意到 所对应的最后一个数字串的字典序必须严格小于当前串才能进行转移。 这时可以按所有长度为 的数字串的字典序升序枚举,用线段树维护前缀最大值。
时间复杂度