长春集训 Day 11 - 模拟赛
长春集训 Day 11 - 模拟赛
85+70+10+30
T1 - 操作序列
Description
给定正整数 \(x\),可以进行如下两种操作:
- \(x \leftarrow 2x-1\);
- 若 \(x\) 为奇数,则 \(x \leftarrow \dfrac{x+1}2\),否则无法进行该操作。
求出长度为 \(n\) 的合法操作序列数对 \(998244353\) 取模的值。
\(1 \le n \le 5000\)。
Solution
容易注意到,这两种操作具有明显的互逆性。具体地,无论 \(x\) 是奇是偶,它先经操作 1 再经操作 2 一定能够回到原数;当 \(x\) 为奇时,它先经操作 2 再经操作 1 才能回到原数。因此不断的进行操作 2,可以得到最终 \(x\) 能递推到的最小值。
然后注意到我们递推的结构类似于一个树状图,而且能够递推出的数很离散,所以显然可以对数种分类 DP。具体地,设 \(dp_{i,j}\) 表示目前递推到第 \(i\) 层,最后一位为第 \(j\) 种数的方案数。显然它只能来源于第 \(i-1\) 层的 \(j-1\) 和 \(j+1\),直接 DP 即可。
需要特判 \(x=1\) 的情况,因为无论它如何操作,数字都不会变。
Code
#include <bits/stdc++.h>
// #pragma GCC optimize(2)
#define int long long
#define inf 1e18
#define debug cout << '!';
#define filein(x) freopen(#x".in", "r", stdin);
#define fileout(x) freopen(#x".out", "w", stdout);
#define file(x) filein(x) fileout(x)
using namespace std;
const int N = 5005, mod = 998244353;
int n, x;
int ans, dp[N][N];
int cnt = 0;
int qpow(int x, int y) {
if (y == 0) return 1;
int tmp = qpow(x, y/2);
if (y&1) return tmp * tmp % mod * x % mod;
return tmp * tmp % mod;
}
signed main() {
cin.tie(0) -> sync_with_stdio(0);
// file(operation);
cin >> n >> x;
if (x == 1) {
cout << qpow(2, n);
return 0;
}
while (x&1) {
x = (x+1) / 2;
cnt++;
}
dp[0][cnt] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= n+cnt; j++) {
dp[i][j] = (dp[i-1][j+1] + dp[i-1][j-1]) % mod;
}
}
for (int i = 0; i <= n+cnt; i++) {
ans = (ans + dp[n][i]) % mod;
}
cout << ans;
return 0;
}
T2 - 考场划分
Description
给定长度为 \(n\) 的数列 \(a\),试将 \(a\) 划分为若干连续非空段,使得段长极差不超过 \(k\),段内元素之和的极差不超过 \(S\),求划分方案数对 \(998244353\) 取模的结果。
\(1 \le n \le 100\)。
Solution
70pts 做法:
段长极差和段内元素之和的极差非常不好维护,但注意到合法的段长只有 \(O(n)\) 种,合法的段内元素之和只有 \(O(n^2)\) 种,所以可以直接枚举。
假设目前枚举的段长为 \(P\),段内元素之和为 \(Q\),记 \(dp_{i}\) 表示考虑到第 \(i\) 位的划分方案数,可以枚举前缀,产生新的段长和段内元素之和来转移。不过,我们还需满足极差的两条限制,所以不能这样简单地定义状态。这时可以人为地加上限制,即钦定:对于目前枚举的 \(P,Q\),所有 \(dp\) 转移过程中枚举到的段长必须属于区间 \([P,P+k]\),段内元素之和必须属于区间 \([Q,Q+S]\)。这样可以自动满足极差的限制。但这样不能确定段长和段内元素之和恰好为 \(P\) 和 \(Q\) 的状态值,所以我们加两维,定义出 \(dp_{i,0/1,0/1}\),表示目前状态中是否出现了恰好为 \(P\) 和 \(Q\) 的段长或段内元素之和。最终,对于每对枚举的 \(P,Q\),我们将 \(dp_{n,1,1}\) 纳入对答案的贡献即可。时间复杂度是 \(O(n^5)\) 的。
可以用前缀和优化到 \(O(n^4)\),但我没有搞明白。
Code
#include <bits/stdc++.h>
// #pragma GCC optimize(2)
#define int long long
#define inf 1e18
#define debug cout << '!';
#define filein(x) freopen(#x".in", "r", stdin);
#define fileout(x) freopen(#x".out", "w", stdout);
#define file(x) filein(x) fileout(x)
using namespace std;
const int N = 105, mod = 998244353;
int n, k, S;
int a[N], sum[N], ans;
int dp[N][2][2];
signed main() {
cin.tie(0) -> sync_with_stdio(0);
file(exam);
cin >> n >> k >> S;
for (int i = 1; i <= n; i++) {
cin >> a[i];
sum[i] = sum[i-1] + a[i];
}
vector<int> sums;
for (int l = 1; l <= n; l++) {
for (int r = l; r <= n; r++) {
sums.push_back(sum[r] - sum[l-1]);
}
}
sort(sums.begin(), sums.end());
sums.erase(unique(sums.begin(), sums.end()), sums.end());
for (int P = 1; P <= n; P++) {
for (int Q : sums) {
memset(dp, 0, sizeof dp);
dp[0][0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < i; j++) {
// for that: len == P, sum == Q
/*
1 j i
------------
===================
cur_P = i-j+1, cur_Q = s_i - s_{j-1}
*/
int cur_P = i-j, cur_Q = sum[i] - sum[j];
if (cur_P < P or cur_P > P + k) continue;
if (cur_Q < Q or cur_Q > Q + S) continue;
bool ext_P = (cur_P == P);
bool ext_Q = (cur_Q == Q);
for (int p = 0; p <= 1; p++) {
for (int q = 0; q <= 1; q++) {
dp[i][p|ext_P][q|ext_Q] = (dp[i][p|ext_P][q|ext_Q] + dp[j][p][q]) % mod;
}
}
}
}
ans = (ans + dp[n][1][1]) % mod;
}
}
cout << ans;
return 0;
}
T3 - 区间操作
Description
给定长度为 \(n\) 的序列 \(a\)。对于区间 \([l,r]\),初始持有数字 \(s=0\),接下来可以进行无限次操作,每次操作选中一个 \(a_x(x \in [l,r])\),令 \(s,a_x \leftarrow \dfrac{s+a_x}2\)。可以证明,能通过适当的策略获得最大的 \(s\)。
已知在这 \(\dfrac{n(n+1)}{2}\) 种选择方案中,计算所有方案下能够获得的最大的 \(s\) 的加和对 \(998244353\) 取模的结果。
\(1 \le n \le 5 \times 10^5\)。
Solution
容易发现,对于任意区间,最大值总和等于所有元素在该区间内按降序排列后贡献的权重和。形式化地,设区间 \([l,r]\) 内的元素从大到小排序得到 \(b_1 \ge b_2 \ge \cdots \ge b_m\),则
据此,第 \(k\) 大的数贡献权重为 \(\dfrac 1 {2^k}\)。
固定元素 \(a_i\),考虑所有包含它的区间 \([l,r]\),记 \([l,i-1]\) 中大于 \(x\) 的数的个数为 \(L(l)\);右侧 \([i+1,r]\) 中大于 \(x\) 的数的个数为 \(R(r)\),则该元素在所有包含它的区间上产生的贡献为
需要快速计算括号里的两项。
考虑一个动态的思想:一边往数组中加元素,一边计算答案。可以将数组排序,然后从大到小依次处理每个元素。在原数组上种植一棵线段树,维护:
- 节点区间的带权和,即 \(\sum \dfrac 1{2^k}\),其中 \(k\) 为排名。
- 节点区间内已插入的元素个数,即大于当前值的元素数量。
然后简单搞一搞就能实现查询了。
Code
#include <bits/stdc++.h>
// #pragma GCC optimize(2)
#define int long long
#define inf 1e18
#define debug cout << '!';
#define filein(x) freopen(#x".in", "r", stdin);
#define fileout(x) freopen(#x".out", "w", stdout);
#define file(x) filein(x) fileout(x)
using namespace std;
const int N = 5e5+5, mod = 998244353;
const int inv2 = (mod + 1) / 2;
int n, p2[N], ip2[N];
struct Element {
int val, id;
friend bool operator < (Element x, Element y) {
return x.val < y.val;
}
} a[N];
struct SegmentTree {
#define mid ((l + r) / 2)
#define lson (id << 1)
#define rson (id << 1 | 1)
int tcnt = 0;
struct Node {
int sum, cnt; // 加权和;区间内插入的元素数
} t[N << 2];
void pushup(int id) {
t[id].cnt = t[lson].cnt + t[rson].cnt;
t[id].sum = (t[lson].sum * ip2[t[rson].cnt] + t[rson].sum) % mod;
}
void build(int id, int l, int r) {
t[id].sum = 1;
t[id].cnt = 0;
if (l == r) {
return;
} else {
build(lson, l, mid);
build(rson, mid + 1, r);
pushup(id);
}
}
void modify(int id, int l, int r, int pos) {
if (l == r) {
t[id].sum = t[id].sum * inv2 % mod;
t[id].cnt++;
return;
} else {
if (pos <= mid) modify(lson, l, mid, pos);
else modify(rson, mid + 1, r, pos);
pushup(id);
}
}
int query(int id, int l, int r, int qr) {
if (l > qr) return 0;
if (r <= qr) {
tcnt += t[id].cnt;
return t[id].sum * ip2[t[1].cnt - tcnt] % mod;
}
return (query(lson, l, mid, qr) + query(rson, mid + 1, r, qr)) % mod;
}
int query(int qr) {
tcnt = 0;
return query(1, 1, n, qr) * p2[t[1].cnt - tcnt] % mod;
}
#undef mid
#undef lson
#undef rson
} segl, segr;
signed main() {
cin.tie(0) -> sync_with_stdio(0);
// file(sequence);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i].val;
a[i].id = i;
}
p2[0] = ip2[0] = 1;
for (int i = 1; i <= n; i++) {
p2[i] = p2[i-1] * 2 % mod;
ip2[i] = ip2[i-1] * inv2 % mod;
}
sort(a+1, a+1+n);
segl.build(1, 1, n);
segr.build(1, 1, n);
int ans = 0;
for (int i = n; i >= 1; i--) {
ans = (ans + segl.query(a[i].id) * segr.query(n-a[i].id+1) % mod * a[i].val) % mod;
segl.modify(1, 1, n, a[i].id);
segr.modify(1, 1, n, n-a[i].id+1);
}
cout << ans * inv2 % mod;
return 0;
}

浙公网安备 33010602011771号