AtCoder Beginner Contest 367

待补:ABC 367 G 题解 - spdarkle - 博客园 (cnblogs.com)

D.一个湖周围有 NN 个休息区。
这些休息区按顺时针顺序编号为 1122 、…、 NN
从休息区 ii 顺时针走到休息区 i+1i+1 需要 AiA_i 步。(其中休息区 N+1N+1 指的是休息区 11 )。
从休息区 ss 顺时针走到休息区 ttsts \neq t )所需的最小步数是 MM 的倍数。
(s,t)(s,t) 的可能对数。

Sol:注意只能顺时针,所以固定起点的时候,到达任何一个点的路程都是确定的。考虑破环成链,维护%mod意义下的前缀和一个map桶,像滑动窗口维护这个map。但要注意边界,因aia_{i}表示的是一条边。

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
void solve() {
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = n + 1; i <= 2 * n; i++) a[i] = a[i - n];
map<int, int> mp;
for (int i = 1; i <= 2 * n; i++) a[i] += a[i - 1], a[i] %= m;
int ans = 0;
// int sum = 0;
//for (int i = 0; i <= 2 * n; i++) cerr << a[i] << " ";
// cerr << endl;
// deb(0);
// for (int i = 0; i <= n - 1; i++) cerr << a[i] << " ";
//cerr << endl;

for (int i = 0; i <= n - 1; i++) mp[a[i]]++;

ans = mp[0]-1;
//deb(ans,mp);
for (int i = 1; i <= n - 1; i++) {
//deb(i);
//for (int j = i; j <= i + n - 1; j++) cerr << a[j] << " ";
//cerr << endl;
mp[a[i - 1]]--;
if (mp[a[i - 1]] == 0)
mp.erase(a[i - 1]);
mp[a[i + n - 1]]++;
ans += mp[a[i]]-1;
//deb(ans,mp);
}
cout << ans << endl;
}

E:基环树倍增模板:

给你一个长度为 NN 的序列 XX ,其中每个元素都介于 11NN 之间(含),以及一个长度为 NN 的序列 AA
打印在 AA 上执行以下操作 KK 次的结果。

  • BB 替换 AA ,使得 Bi=AXiB_i = A_{X_i} .

Sol:发现可以建图i向xix_{i}连边。然后形成了内向基环树森林。线性dp做法的实现难度较大,倍增对于在环上走很多步非常有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int n, m;
int a[N];
int x[N];
int st[N][65];
void solve() {
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> x[i];
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) st[i][0] = x[i];

for (int j = 1; j <= 60; j++) {
for (int i = 1; i <= n; i++) {
st[i][j] = st[st[i][j - 1]][j - 1];
}
}
for (int i = 1; i <= n; i++) {
int pos = i;
for (int j = 60; j >= 0; j--) {
if ((m >> j) & 1)
pos = st[pos][j];
}
cout << a[pos] << " ";
}
}

F.

  • 序列哈希板子:考虑对于一个序列,如果我们发现每次关心的都是其排序后结果,这意味着我们不在乎出现位置。我们考虑维护值域大小为M的桶(string代替),如果是集合那么其实是个01字符串。如果是可重集合,那么字符串第i个位置有cnt[i],表示这个可重集合中i这个值出现了cnt[i]次。选择一个质数base,记第i个位置的哈希值贡献为base cnt[i]base \ ^{cnt[i]}.

题意:给你长度为 NN 的正整数序列: A=(A1,A2,,AN)A=(A_1,A_2,\ldots,A_N)B=(B1,B2,,BN)B=(B_1,B_2,\ldots,B_N) .

您需要依次处理 QQ 个查询。 ii -th 查询的解释如下。

  • 给定正整数 li,ri,Li,Ril_i,r_i,L_i,R_i 。如果可以重新排列子序列 (Ali,Ali+1,,Ari)(A_{l_i},A_{l_i+1},\ldots,A_{r_i}) 以匹配子序列 (BLi,BLi+1,,BRi)(B_{L_i},B_{L_i+1},\ldots,B_{R_i}) ,则打印 “是”,否则打印 “否”。
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
constexpr u64 P = u64(1E18) + 9;
//constexpr u64 P = 998244353;
using Z = ModInt64<P>;
int n, m;
int a[N];
int b[N];
Z hsa[N], hsb[N];
random_device rd;
mt19937_64 gen(rd());
static int findprime() {
int n = gen() % 1000000 + 1e6;
if (n % 2 == 0)
n++;
while (true) {
bool ok = 1;
for (int i = 3; i * i <= n; i += 2) {
if (n % i == 0) {
ok = 0;
n += 2;
break;
}
}
if (ok)
return n;
}
}
Z pwn[N];
const ull base = findprime();
void solve() {
cin >> n >> m;
pwn[0] = (u64)1;
for (int i = 1; i <= N - 2; i++) pwn[i] = (pwn[i - 1] * base);
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++) {
hsa[i] = hsa[i - 1] + pwn[a[i]];
hsb[i] = hsb[i - 1] + pwn[b[i]];
}
for (int i = 1; i <= m; i++) {
int l1, l2, r1, r2;
cin >> l1 >> r1 >> l2 >> r2;
if (hsa[r1] - hsa[l1 - 1] == hsb[r2] - hsb[l2 - 1])
cout << "Yes" << endl;
else
cout << "No" << endl;
}
}