[算法训练] ABC468-①C-DFS/托康展开②D-中心扩展③E-贡献法+逆元+前缀和
ABC468-①C-DFS/托康展开②D-中心扩展③E-贡献法+逆元+前缀和
Tasks - AtCoder Beginner Contest 468
https://www.wolai.com/3tQDqVUDZjQfGd14L8ppmE
C-DFS/康托展开
题目
给你一个整数 和整数序列 和 ,每个序列都是 的排列。
请问有多少个整数序列是 的置换序列,且其序数大于 ,小于 。
什么是整数序列的词序?
对于整数序列 和 ,如果下面的 或 成立,我们就说 在词典上小于 。这里, 分别表示 的长度。
- 和 。
- 存在一个整数 ,使得以下两个条件都成立。
- 在数值上小于 。
DFS
排列型DFS
def dfs(n, p, q): used = [False] * (n + 1) ans = 0 def dfs(pos, cur): nonlocal ans if pos == n: if p < cur < q: ans += 1 return for num in range(1, n + 1): if not used[num]: if cur and cur < p[:pos] and cur > q[:pos]: continue used[num] = True cur.append(num) dfs(pos + 1, cur) cur.pop() used[num] = False
dfs(0, []) return ansn = int(input())p = list(map(int,input().split()))q = list(map(int,input().split()))print(dfs(n,p,q))#include <bits/stdc++.h>using namespace std;
int dfs(int n, vector<int>& p, vector<int>& q) { vector<bool> used(n + 1, false); int ans = 0;
function<void(int, vector<int>&)> dfs_impl = [&](int pos, vector<int>& cur) { if (pos == n) { if (cur > p && cur < q) { ans++; } return; } for (int num = 1; num <= n; num++) { if (!used[num]) { // 剪枝:如果前缀已经不可能在区间内 bool prefix_ok = true; if (pos > 0) { bool less_than_p = true, greater_than_q = true; for (int k = 0; k < pos; k++) { if (cur[k] >= p[k]) less_than_p = false; if (cur[k] <= q[k]) greater_than_q = false; } if (less_than_p && greater_than_q) { prefix_ok = false; } } if (!prefix_ok) continue;
used[num] = true; cur.push_back(num); dfs_impl(pos + 1, cur); cur.pop_back(); used[num] = false; } } };
vector<int> cur; dfs_impl(0, cur); return ans;}
int main() { int n; cin >> n; vector<int> p(n), q(n); for (int i = 0; i < n; i++) cin >> p[i]; for (int i = 0; i < n; i++) cin >> q[i];
cout << dfs(n, p, q) << endl; return 0;}库函数
使用permutations直接生成全排列
from itertools import permutationsn = int(input())p = list(map(int, input().split()))q = list(map(int, input().split()))ans = 0for a in permutations([i + 1 for i in range(n)]): ans += p < list(a) < qprint(ans)康托展开
定义
康托展开是一个双射,它将一个排列映射为一个整数,该整数表示该排列在所有排列中的字典序排名(从 0 开始)。
对于一个排列 :
其中 = 在 X[i] 后面出现的、比 X[i] 小的数的个数。
等价于:在当前未使用的数字中,有多少个比 X[i] 小的数。
def cantor_rank(X): N = len(X) fact = [1] * (N + 1) for i in range(1, N + 1): fact[i] = fact[i-1] * i
rank = 0 used = [False] * (N + 1)
for i in range(N): # 统计未使用且小于 X[i] 的数字个数 cnt = 0 for num in range(1, X[i]): if not used[num]: cnt += 1
rank += cnt * fact[N - i - 1] used[X[i]] = True
return rank把排列看作一个 N 位数字,每位可以选剩余未用的数字:
第1位:有 N 种选择 → rank 变化量 = 选第 k 小的数,跳过 (k-1) × (N-1)! 个排列第2位:有 N-1 种选择 → rank 变化量 = 选第 k 小的数,跳过 (k-1) × (N-2)! 个排列...本质:康托展开就是计算”有多少个排列排在它前面”。
逆康托展开
给定 rank,还原排列:
def cantor_inverse(N, rank): fact = [1] * (N + 1) for i in range(1, N + 1): fact[i] = fact[i-1] * i
available = list(range(1, N + 1)) res = []
for i in range(N): idx = rank // fact[N - i - 1] rank %= fact[N - i - 1] res.append(available.pop(idx))
return res- 排列哈希:将排列压缩为一个整数()
- 排列的第 k 个:逆康托展开求排名第 k 的排列
- 区间计数:统计字典序在某区间的排列数
- 状态压缩:在 BFS/A* 中表示排列状态(如八数码问题)
MOD = 998244353class Fenwick: def __init__(self, n): self.n = n self.bit = [0] * (n + 1)
def add(self, idx, val): while idx <= self.n: self.bit[idx] += val idx += idx & -idx
def sum(self, idx): res = 0 while idx > 0: res += self.bit[idx] idx -= idx & -idx return resdef tuo_kang(a): n = len(a) fact = [1] * (n + 1) for i in range(1, n + 1): fact[i] = fact[i - 1] * i % MOD bit = Fenwick(n) for i in range(1, n + 1): bit.add(i, 1) rank = 0 for i in range(n): cnt = bit.sum(a[i] - 1) # 未使用且小于 a[i] 的个数 rank = (rank + cnt * fact[n - i - 1]) % MOD bit.add(a[i], -1) # 标记 a[i] 已使用 return (rank + 1) % MODdef main(): n = int(input()) a = list(map(int,input().split())) b = list(map(int,input().split())) print(tuo_kang(b) - tuo_kang(a) - 1 if tuo_kang(b) - tuo_kang(a) - 1 > 0 else 0)if __name__ == "__main__": main()D-中心扩展
题目
如果一个由小写英文字母组成的字符串满足以下条件,它就被称为好字符串。
- 它最多可以通过改写一个字符变成一个回文字符串。
例如,a、iwai和abcdcza是好字符串,但abcd和atcoder不是好字符串。特别要注意的是,重码字符串也是好字符串。
给您一个由小写英文字母组成的字符串 。
请找出 的非空子串(连续子序列)中有多少个是好字符串。
从 的不同位置取出的两个子串即使作为字符串相等,也要分别计算。
什么是子串?
的子串是删除 开头的零个或多个字符和结尾的零个或多个字符后得到的字符串。
例如,ab是abc的子串,但ac不是abc的子串。
中心扩展
找出好字符串的中间字符在整个搜索中的位置。让这个中间字符成为第 个字符。
中心为 长度为 的子串是一个好字符串,这一事实等同于满足 的 的个数小于 。因此,只需确定在 中升序为 的 的个数是否小于 即可。用同样的方法可以确定偶数长度的好字符串的个数。
s = input().strip()n = len(s)ans = 0for i in range(n): diff = 0 l, r = i, i while l >= 0 and r < n: if s[l] != s[r]: diff += 1 if diff <= 1: ans += 1 else: break l -= 1 r += 1for i in range(n - 1): diff = 0 l, r = i, i + 1 while l >= 0 and r < n: if s[l] != s[r]: diff += 1 if diff <= 1: ans += 1 else: break l -= 1 r += 1print(ans)s = input()n = len(s)ans = 0for k in range(2): for st in range(n): l,r = st - k,st cnt = 0 while 0 <= l and r < n: if s[l] != s[r]: cnt += 1 if cnt == 2: break l -= 1 r += 1 ans += 1print(ans)E-贡献法+逆元+前缀和
题目
给定序列 A,定义 为子数组 A[l..r] 的算术平均值。
求所有子数组的平均值之和,答案对 998244353 取模。
贡献法+逆元+前缀和
交换求和顺序
原问题:
考虑每个 A[i] 对答案的贡献:
括号内的部分记为 coeff[i],即位置 i 的贡献系数。
令子数组长度 k = r - l + 1:
- 左端点
l范围:max(1, i - k + 1)到min(i, N - k + 1) - 长度
k范围:1到N
coeff[i] = 所有包含 i 的子数组的 之和。
前缀和优化
定义:
inv[k]:k 的模逆元S[x] = inv[1] + inv[2] + ... + inv[x]
则对于固定的 l 和 r:
coeff[i] =
二重前缀和
令 pref[i] = S[1] + S[2] + ... + S[i](S 的前缀和)
经过推导:
(其中 pref[0] = 0)
- 预处理逆元
inv[1..N] - 前缀和
S[1..N] - 二重前缀和
pref[1..N] - 遍历
i,计算coeff[i] = pref[N] - pref[N-i] - pref[i-1] - 累加
A[i] * coeff[i]
MOD = 998244353n = int(input())a = list(map(int, input().split()))inv = [0] * (n + 1)inv[1] = 1for i in range(2, n + 1): inv[i] = MOD - MOD // i * inv[MOD % i] % MODs = [0] * (n + 1)for i in range(1, n + 1): s[i] = (s[i - 1] + inv[i]) % MODpre = [0] * (n + 1)for i in range(1, n + 1): pre[i] = (pre[i - 1] + s[i]) % MODans = 0for i in range(n): dif = (pre[n] - pre[n - i - 1] - pre[i]) % MOD ans = (ans + a[i] * dif) % MODprint(ans)文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!