USACO2023JAN G 比赛笔记

打得还行,希望能晋级P。

T1. Find and Replace

有一个字符串 $S$,初始为单个字符 a。有 $n$ 次操作,每次给定字符 $c$ 和字符串 $s$,将 $S$ 中所有 $c$ 替换为 $s$。操作结束后给定 $l,r$,询问 $S_{l\dots r}$。
$\sum \lvert s \rvert \le 2\cdot 10^5,r-l+1\le 2\cdot 10^5$

Tag: 搜索

设 $f_{i,j}$ 表示字符 $i$ 经过操作 $j,j+1\dots n$ 后会变为多少个字符。则可以根据 $f$ 的值 DFS(类似线段树查询,一层层拆分区间,$f$ 的值即为区间大小)。

这样会遇到一个问题:如果存在大量 $\lvert s \rvert =1$,即一个字符变为一个字符,那么 DFS 中操作次数可能达到 $O(n^2)$ 次。对此,可以预处理出字符 $i$ 在操作 $j$ 后第一个 $\lvert s \rvert >1$ 的操作 $nxt_{i,j}$,以及在操作 $nxt_{i,j}$ 时字符 $i$ 变成了字符 $to_{i,j}$。这样可以快速跳转至下一个增加 $S$ 长度的操作。

由于每次有效操作都会将 $S$ 长度至少+1,而 $r-l+1\le 2\cdot 10^5$,所以需要处理的操作也不会超过这个数。

Code

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
char c[N];
string s[N];
ll dp[26][N], nxt[26][N], to[26][N];
ll L, R, n;
void dfs(ll x, ll d, ll p, ll l, ll r) {
// cout << x << ' ' << d << endl;
if (d == n) {
cout << char('a' + x);
return;
}
rep(i, s[d].size()) {
int y = s[d][i] - 'a';
ll w = dp[y][d + 1];
if (p <= r && p + w - 1 >= l)dfs(to[y][d + 1], nxt[y][d + 1], p, max(p, l), min(p + w - 1, r));
p += w;
if (p > r)break;
}
}
void solve() {
cin >> L >> R >> n;
rep(i, n)cin >> c[i] >> s[i];
rep(i, 26)dp[i][n] = 1, nxt[i][n] = n, to[i][n] = i;
for (int j = n - 1;j >= 0;--j) rep(i, 26) {
if (c[j] - 'a' != i) {
dp[i][j] = dp[i][j + 1];
nxt[i][j] = nxt[i][j + 1];
to[i][j] = to[i][j + 1];
}
else if (s[j].size() == 1) {
dp[i][j] = dp[s[j][0] - 'a'][j + 1];
nxt[i][j] = nxt[s[j][0] - 'a'][j + 1];
to[i][j] = to[s[j][0] - 'a'][j + 1];
}
else {
dp[i][j] = 0, nxt[i][j] = j, to[i][j] = i;
rep(k, s[j].size()) {
dp[i][j] += dp[s[j][k] - 'a'][j + 1];
dp[i][j] = min(dp[i][j], (ll)MAXN * MAXN);
}
}
}
dfs(0, 0, 1, L, R);
}

T2. Lights Off

Tag: 数学

有两个二进制数 $a,b$,每次操作,先将 $b$ 的一位取反,然后将 $a$ 异或上 $b$,最后将 $b$ 在二进制下循环右移一位。问至少几次操作让 $a=0$。
多测,数据组数 $\le 2\cdot 10^5$,所有测试点中 $a,b$ 在二进制下位数相同且 $\le 20$。

考虑将一位反转会对结果造成什么影响,发现一定是异或上连续的一串1,而由于每次操作内只能反转一次,所以每种长度的一串1有且仅有一个。于是可以预处理一个背包 $f_{i,j}$ 表示长度为 $1\dots i$ 的串异或起来能否得到 $j$。询问时枚举答案即可。

不太清楚是怎么证明答案 $\le n$ 的。

Code

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
int T_ = 1, case_;
int n;
bool dp[21][1 << 21];
int shift(int x) {
int y = x % 2;
return y * (1 << (n - 1)) + x / 2;
}
string s, t;
int a, b;
void solve() {
cin >> s >> t;
a = b = 0;
rep(i, n)a = a * 2 + s[i] - '0', b = b * 2 + t[i] - '0';
int now = 0;
rep(i, n + 1) {
if (dp[i][(a ^ now)]) {
cout << i << endl;
return;
}
now = shift(now) ^ b;
}
}
int main() {
// ios::sync_with_stdio(0);
// cin.tie(0), cout.tie(0);
cin >> T_ >> n;
dp[0][0] = 1;
int p[21];
init(p, 0);
rep(i, n) {
rep(j, n)p[j] = shift(p[j]) ^ (1 << j);
rep(msk, (1 << n)) {
if (!dp[i][msk])continue;
rep(j, n)dp[i + 1][(msk ^ p[j])] = 1;
}
}
for (case_ = 1; case_ <= T_; case_++)solve();
return 0;
}

T3. Moo Rute

一条长度为 $n+1$ 的链,节点标号为 $0 \dots n$。从 0 开始走,每步向左或向右一个节点。已知每条边的经过次数 $a_i$,求在掉头次数最少的前提下,有多少种方案。
$n \le 10^5,a_i \le 10^6$

只写了个 $n=2$ 的部分分,组合数随便搞搞。

正解不知道怎么做。

UPD 2023/3/24:晋级了

先只考虑 $n=2$ 时的情况:

  • 若 $a_1>a_2$,答案为 $\dbinom{a_1/2}{a_2/2}$
  • 若 $a_1 \le a_2$,答案为 $\dbinom{a_2/2-1}{a_1/2-1}$

稍加思考可以知道,$n$ 更大的情况其实就相当于若干个 $n=2$ 连起来,相乘即可。

Code

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
ll n, a[100005], ans = 1;
ll f[1000006];
ll qpow(ll x, ll y) {
ll p = 1;
while (y) {
if (y & 1)p = p * x % mod;
x = x * x % mod;
y >>= 1;
}
return p;
}
ll C(ll x, ll y) {
return f[x] * qpow(f[y], mod - 2) % mod * qpow(f[x - y], mod - 2) % mod;
}
ll Solve(int a, int b) {
if (a > b)return C(a / 2, b / 2);
else return C(b / 2 - 1, a / 2 - 1);
}
void solve() {
cin >> n;
rep(i, n)cin >> a[i];
f[0] = 1;
repp(i, 1000000)f[i] = f[i - 1] * i % mod;
rep(i, n - 1)ans = ans * Solve(a[i], a[i + 1]) % mod;
cout << ans;
}
作者

Rushroom

发布于

2023-01-28

更新于

2023-03-24

许可协议

CC BY-NC-SA 4.0

评论