神秘题
xiezheyuan · · 题解
神力 / Drunkards
给定一个长度为
n 的序列a ,其中a_i \in \{-1, 1\} 。初始位置为0 。在
i \in [1, n] 的每个时刻,原计划执行的移动操作是使得当前位置增加a_i 。但是,有p 的概率在第i 个时刻不执行移动操作,即位置不变;有1 - p 的概率执行移动操作。各个时刻的操作是否执行相互独立。对于所有可能的终止位置
i \in [-n, n] ,求出最终经过过该位置的概率,结果对10^9+7 取模。
尝试 dp?设
既然容斥不行,考虑钦定计入答案规则来去重,假如我们只让第一次到达
其中
尝试换一个计入答案规则,让最后一次到达
其中
这个怎么算呢?如果
一个直观的想法是设
最后可以直接反推出答案:
特别地,
namespace solution {
const int N = 5005, mod = 1e9 + 7;
using mint = GF<int, mod>;
int n, p_, a[N];
template<class T, int N> struct neg_arr {
T a[N << 1];
T& operator[](int i){ return a[i + N]; }
};
neg_arr<mint, N> f[N], h_min[N], h_max[N];
mint p, q, P[N];
void solve(){
cin >> n >> p_, p = mint(p_) / 100, q = mint(1) - p;
for(int i = 1; i <= n; i++) cin >> a[i];
f[0][0] = 1;
for(int i = 0; i < n; i++){
for(int j = -n; j <= n; j++){
if(!f[i][j].v) continue;
f[i + 1][j + a[i + 1]] += q * f[i][j], f[i + 1][j] += p * f[i][j];
}
}
h_min[n + 1][0] = h_max[n + 1][0] = 1;
for(int i = n; i; i--){
for(int j = -n; j <= n; j++){
if(h_min[i + 1][j].v){
h_min[i][std::min(0, j)] += p * h_min[i + 1][j];
h_min[i][a[i] + std::min(0, j)] += q * h_min[i + 1][j];
}
if(h_max[i + 1][j].v){
h_max[i][std::max(0, j)] += p * h_max[i + 1][j];
h_max[i][a[i] + std::max(0, j)] += q * h_max[i + 1][j];
}
}
}
P[n + 1] = 1;
for(int i = 1; i <= n; i++){
if(a[i] == 1) for(int k = 0; k <= n; k++) P[i] += h_min[i + 1][k];
else for(int k = -n; k <= 0; k++) P[i] += h_max[i + 1][k];
P[i] *= q;
}
for(int i = -n; i <= n; i++){
mint ans = 0;
for(int j = 0; j <= n; j++) ans += f[j][i] * P[j + 1];
std::cout << ans << ' ';
}
std::cout << '\n';
}
}
开关灯 / Flipping Game
有
n 盏灯,初始时灯i 是否亮着记为a_i ,目标状态灯i 是否亮着记作b_i 。选择其中的三盏灯构成无序不重三元组
(x,y,z) ,有N=\binom{n}{3} 种选法,你需要从这N 个三元组种不重复地选出若干个,然后对于每一个选出的三元组(x,y,z) ,按下灯x,y,z 的开关。若可以将初始状态转换为目标状态,则称这个三元组集合是好的,你需要计算大小为m 的好的三元组集合的数量。答案对10007 取模。
先不考虑不重复选三元组的限制,一个最朴素的做法,设
转移的话可以枚举这一步的三元组怎么选:
- 选出的三盏灯在
S 内:f(i+1,j-3)\gets f(i,j)\binom{j}{3} 。 - 选出的两盏灯在
S 内:f(i+1,j-1)\gets f(i,j)\binom{j}{2}(n-j) 。 - 选出的一盏灯在
S 内:f(i+1,j+1)\gets f(i,j)j\binom{n-j}{2} 。 - 选出的零盏灯在
S 内:f(i+1,j+3)\gets f(i,j)\binom{n-j}{3} 。
时间复杂度
对于
意义就是除掉顺序
namespace solution {
const int N = 1005, mod = 10007;
using mint = GF<int, mod>;
int n, m;
mint fact[N], ifact[N], f[N][N];
std::string from, to;
mint binom(int n, int m){ return (n < m || m < 0) ? 0 : fact[n] * ifact[m] * ifact[n - m]; }
bool solve(){
cin >> n >> m >> from >> to; int _ = std::max(n, m);
if(!_) return false;
fact[0] = 1; for(int i = 1; i <= _; i++) fact[i] = fact[i - 1] * i;
ifact[_] = ~fact[_]; for(int i = _; i; i--) ifact[i - 1] = ifact[i] * i;
f[0][0] = 1;
for(int i = 0; i < m; i++){
for(int j = 0; j <= n; j++) f[i + 1][j] = 0;
for(int j = 0; j <= n; j++){
if(j >= 3) f[i + 1][j - 3] += f[i][j] * binom(j, 3);
if(j >= 1) f[i + 1][j - 1] += f[i][j] * binom(j, 2) * (n - j);
f[i + 1][j + 1] += f[i][j] * j * binom(n - j, 2);
f[i + 1][j + 3] += f[i][j] * binom(n - j, 3);
}
if(!i) continue;
for(int j = 0; j <= n; j++) f[i + 1][j] -= f[i - 1][j] * i * (binom(n, 3) - (i - 1));
}
int cnt = 0; for(int i = 0; i < n; i++) cnt += from[i] != to[i];
std::cout << f[m][cnt] * (~binom(n, cnt)) * ifact[m] << '\n';
return true;
}
}
排列问题 / Tiket
给定三个长度为
n 的排列a,b,c ,计算:\sum_{1\leq i,j\leq n}[a_i<a_j][b_i<b_j][c_i<c_j]
看起来是一个经典的三维偏序问题,直接用经典的树套树或者 cdq 分治做,时间复杂度
我们记
我们虽然暂时不会
考虑
惊人的事实:
时间复杂度
namespace solution {
using i64 = int64_t;
const int N = 2e6 + 5;
int n, a[N], b[N], c[N], tmp[N];
i64 seedA, seedB, seedC;
i64 rand(i64& seed){ return seed = ((seed * 19260817) ^ 233333) & ((1 << 24) - 1); }
struct BIT {
int t[N];
void clear(){ std::fill(t + 1, t + n + 1, 0); }
void update(int p, int v){ for(; p <= n; p += p & -p) t[p] += v; }
int query(int p){ int res = 0; for(; p; p -= p & -p) res += t[p]; return res; }
} bit;
i64 paritial2d(int* a, int* b){
for(int i = 1; i <= n; i++) tmp[i] = i;
std::sort(tmp + 1, tmp + n + 1, [=](int x, int y){ return a[x] < a[y]; });
bit.clear(); i64 res = 0;
for(int i = 1; i <= n; i++) res += bit.query(b[tmp[i]]), bit.update(b[tmp[i]], 1);
return res;
}
void solve(){
cin >> n >> seedA >> seedB >> seedC;
for(int i = 1; i <= n; i++) a[i] = b[i] = c[i] = i;
for(int i = 1; i <= n; i++) std::swap(a[i], a[rand(seedA) % i + 1]);
for(int i = 1; i <= n; i++) std::swap(b[i], b[rand(seedB) % i + 1]);
for(int i = 1; i <= n; i++) std::swap(c[i], c[rand(seedC) % i + 1]);
i64 ans = paritial2d(a, b) + paritial2d(b, c) + paritial2d(a, c);
ans -= 1ll * n * (n - 1) / 2;
std::cout << (ans / 2) << '\n';
}
}
数数 / Fancy Arrays
给定
m 和q ,q 次询问有多少个长度为n 的数列A 满足所有元素都是 m 的因子且任意相邻两数不互质,答案对998244353 取模。
一个朴素的 dp 是记
由于发现每一次转移都形如
首先可以观察到我们不需要记录具体的因数,事实上只需要记录
我们考察
- 记
e_i=j 的p_i 数量为c_i ,G(m)=\prod_i (c_i+1) 。
一个较为简洁的上界估计是
以上都是做法的大致轮廓,我们要将大致想法变成具体思路。首先对
尝试构建转移矩阵
然后尝试减去与
然后我们要统计答案,只需要计算
注意到我们要计算的是向量 * 矩阵的若干次幂的形式,并且询问较多(
namespace solution {
const int mod = 998244353;
using mint = GF<int, mod>;
using i64 = long long;
using i128 = __int128;
i64 n, m, q;
mint fact[255], ifact[255];
mint binom(int n, int m){ return (n < m || m < 0) ? 0 : fact[n] * ifact[m] * ifact[n - m]; }
bool miller_rabin(i64 p){
static constexpr int pris[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37};
static auto fastpow = [](i64 a, i64 b, i64 p){
i64 res = 1; for(; b; b >>= 1, a = i128(a) * a % p) if(b & 1) res = i128(res) * a % p;
return res;
};
i64 t, k; for(k = 0, t = p - 1; !(t & 1); k++, t >>= 1);
for(int i : pris){
if(p == i) return true;
i64 a = fastpow(i, t, p), b = a;
for(int j = 1; j <= k; j++){
b = i128(a) * a % p;
if(b == 1 && a != 1 && a != p - 1) return false;
a = b;
}
if(a != 1) return false;
}
return true;
}
i64 pollard_rho(i64 n){
if(!(n % 2)) return 2;
if(!(n % 3)) return 3;
static std::mt19937_64 rnd(std::random_device{}());
std::uniform_int_distribution<i64> gen(1, n - 1);
while(true){
i64 c = gen(rnd), s = 0, t = 0, val = 1;
auto f = [=](i64 x){ return (i128(x) * x + c) % n; };
for(i64 i = 1; ; i <<= 1, s = t, val = 1){
for (i64 j = 1; j <= i; j++){
t = f(t);
i64 diff = t > s ? t - s : s - t;
val = i128(val) * diff % n;
if (!(j & 127)) {
i64 d = std::__gcd(val, n);
if (d > 1 && d < n) return d;
}
}
i64 d = std::__gcd(val, n);
if (d > 1 && d < n) return d;
if(d == n) break;
}
}
}
std::map<i64, int> pri_fact;
std::map<int, int> ed;
std::basic_string<int> es;
void dfs_pri_exp(i64 u){
if(u == 1) return;
if(pri_fact.count(u) || miller_rabin(u)) return pri_fact[u]++, void();
i64 v = pollard_rho(u);
dfs_pri_exp(v), dfs_pri_exp(u / v);
}
struct matrix {
std::vector<std::vector<mint>> a;
int n, m;
matrix() = default;
void resize(int _n, int _m){ n = _n, m = _m, a.clear(), a.resize(n, std::vector<mint>(m)); }
matrix(int n, int m){ resize(n, m); }
matrix(std::vector<std::vector<mint>> a) : a(a), n(a.size()), m(a[0].size()) {}
std::vector<mint>& operator[](int i) { return a[i]; }
const std::vector<mint>& operator[](int i) const { return a[i]; }
};
matrix operator*(const matrix& a, const matrix& b){
matrix c(a.n, b.m);
for(int i = 0; i < a.n; i++){
for(int k = 0; k < a.m; k++){
for(int j = 0; j < b.m; j++) c[i][j] += a[i][k] * b[k][j];
}
}
return c;
}
matrix F, pw[65];
std::basic_string<int> stat[255]; int k; mint f[255];
void dfs_stat(size_t p, std::basic_string<int> s){
if(p == es.size()) return stat[k++] = s, void();
for(int i = 0; i <= ed[es[p]]; i++) dfs_stat(p + 1, s + i);
}
void solve(){
cin >> m >> q, dfs_pri_exp(m); int allp = 0;
for(auto [_, e] : pri_fact) ed[e]++, allp += e;
for(auto [e, _] : ed) es += e;
dfs_stat(0, {});
fact[0] = 1; for(int i = 1; i <= allp; i++) fact[i] = fact[i - 1] * i;
ifact[allp] = ~fact[allp]; for(int i = allp; i; i--) ifact[i - 1] = ifact[i] * i;
F.resize(k, k);
for(int S = 0; S < k; S++){
f[S] = 1;
for(int i = 0; i < es.size(); i++){
int cnt = ed[es[i]], exp = es[i];
f[S] *= binom(cnt, stat[S][i]) * (mint(exp) ^ stat[S][i]);
}
}
for(int S = 1; S < k; S++){
for(int T = 1; T < k; T++){
mint coprime = 1;
for(size_t i = 0; i < es.size(); i++){
int cnt = ed[es[i]];
coprime *= (binom(cnt - stat[T][i], stat[S][i]) / binom(cnt, stat[S][i]));
}
F[S][T] = f[T] * (mint(1) - coprime);
}
}
pw[0] = F; for(int i = 1; i < 64; i++) pw[i] = pw[i - 1] * pw[i - 1];
while(q--){
cin >> n;
matrix G(1, k);
for(int i = 0; i < k; i++) G[0][i] = f[i];
for(int i = 63; ~i; i--) if(((n - 1) >> i) & 1) G = G * pw[i];
mint ans = 0; for(int i = 0; i < k; i++) ans += G[0][i];
// There is a bug in the official data for this question, so we need to subtract 1 here. Please note that subtracting 1 is only to fit the result of the official data, not to subtract one to get the correct answer.
if(n == 1) ans -= 1;
std::cout << ans << '\n';
}
}
}
Student's Camp
有一个
(n+2) \times m 的网格图,其中第一层和最后一层的所有单元格始终存在。中间n 层的每一层初始均有m 个单元格。在k 轮中,每一层左右两端的单元格各有p 的概率被移除。求最终这n+2 层单元格构成的图形是连通的概率,对10^9+7 取模。
dp,设
边界
尝试优化
那么我们实际上只需要处理这三个和式的信息就好了:
根据对称性,很容易得到
这样状态数就是
然后处理出
点亮 / The Mighty Spell
给定一个长度为
n 的颜色序列c ,其中c_i \in [1, m] 。序列中每个位置有1/2 的概率被选中。若存在某种颜色在所有被选中的位置中均未出现,则权值为
0 。否则,权值为所有被选中位置组成的连续段的贡献之和。若一个连续段长度为l ,其贡献为f(l)=2l^3 + 3l^2 + 3l + 3 。求所有选法的期望权值,答案乘上
2^n 后对10^9 + 7 取模。m\leq 50 。
利用期望的线性性,我们只需要研究每一个区间,钦定它成为连续段的贡献即可,设钦定的这个区间为
-
这个区间全部被选中,概率为
(1/2)^{r-l+1} 。 -
这个区间的贡献,即
f(r-l+1) 。 -
其余部分被选中的囊括了其他颜色,概率为(记
S[l,r] 为[l,r] 中出现过的颜色集合):\prod_{i=1}^m [i\not\in S[l,r]] \left(1-\frac{1}{2^{\sum_j [c_j=i]}}\right)
当然直接讲这三部分乘起来是不对的,因为我们还必须保证
优化到更低复杂度前,我们必须规避这个恶心的分类讨论。一个高明的想法是,我们进行一种类似反演的操作,设:
由于我们实际上会将
我们不妨枚举
namespace solution {
const int N = 2e5 + 5, M = 55, mod = 1e9 + 7;
int n, m, a[N], cnt[M], bkt[M];
using mint = GF<int, mod>;
mint w[M], wi[M], F[N];
mint f(mint x){ return x * x * x * 2 + x * x * 3 + x * 3 + 3; }
mint g(mint x){
if(x == 1) return f(x);
if(x == 2) return f(x) - f(x - 1) * 2;
return f(x) - f(x - 1) * 2 + f(x - 2);
}
void solve(){
cin >> n >> m; mint all = 1;
for(int i = 1; i <= n; i++) cin >> a[i], cnt[a[i]]++;
for(int i = 1; i <= m; i++){
w[i] = mint(1) - ((~mint(2)) ^ cnt[i]), wi[i] = ~w[i];
all *= w[i];
}
for(int i = 1; i <= n; i++) F[i] = F[i - 1] + ((~mint(2)) ^ i) * g(i);
std::set<int, std::greater<>> s;
mint ans = 0;
for(int r = 1; r <= n; r++){
if(bkt[a[r]]) s.erase(bkt[a[r]]);
s.insert(bkt[a[r]] = r);
mint tmp = all;
for(auto it = s.begin(); it != s.end(); it++){
int lr = *it, ll = std::next(it) == s.end() ? 1 : (*std::next(it) + 1);
tmp *= wi[a[lr]], ans += (F[r - ll + 1] - F[r - lr]) * tmp;
}
}
std::cout << (ans * (mint(2) ^ n)) << "\n";
}
}
矩阵计数 / Lost Table
给定一个长度为
n 的序列R 和一个长度为m 的序列C ,计算非负整数构成的n\times m 矩阵数量,满足行i 最大值为R_i ,列i 最大值为C_i 。答案对10^9+7 取模。
交换
考察每一个
我们要在每个单元格放
至少有一个很难做(行列有重叠),但是一个也没有就很好做,那么不妨容斥,枚举
直接做时间复杂度
这样就可以做到
namespace solution {
const int N = 2e5 + 5, mod = 1e9 + 7;
using mint = GF<int, mod>;
mint fact[N], ifact[N];
int n, m, R[N], C[N], bkt[N << 1];
mint binom(int n, int m){ return m < 0 || n < m ? 0 : fact[n] * ifact[m] * ifact[n - m]; }
void solve(){
cin >> n >> m; int _ = std::max(n, m);
for(int i = 1; i <= n; i++) cin >> R[i], R[i]--, bkt[i] = R[i];
for(int i = 1; i <= m; i++) cin >> C[i], C[i]--, bkt[i + n] = C[i];
std::sort(R + 1, R + n + 1), std::sort(C + 1, C + m + 1);
std::sort(bkt + 1, bkt + n + m + 1);
int tot = std::unique(bkt + 1, bkt + n + m + 1) - bkt - 1;
fact[0] = 1; for(int i = 1; i <= _; i++) fact[i] = fact[i - 1] * i;
ifact[_] = ~fact[_]; for(int i = _; i; i--) ifact[i - 1] = ifact[i] * i;
mint ans = 1;
for(int i = 1, p = 1, q = 1; i <= tot; i++){
int a = 0, b = 0, n_ = n - p + 1, m_ = m - q + 1;
while(p <= n && R[p] == bkt[i]) b++, p++;
while(q <= m && C[q] == bkt[i]) a++, q++;
mint ret = 0, d = mint(bkt[i]) / mint(bkt[i] + 1);
int S = (1ll * n_ * a + 1ll * b * m_ - 1ll * a * b) % (mod - 1);
for(int i = 0; i <= a; i++){
ret += binom(a, i) * (d ^ (1ll * n_ * i % (mod - 1))) * ((mint(1) - (d ^ (m_ - i))) ^ b) * (i & 1 ? mod - 1 : 1);
}
ans *= ret * (mint(bkt[i] + 1) ^ S);
}
std::cout << ans << '\n';
}
}
棒棒糖
给定
N, M, x 。对于所有1 \le n \le N, 1 \le m \le M ,求满足\sum_{i=1}^n a_i = m 的自然数序列a 的“前x 大元素之和”的总和。答案对
998244353 取模。
看不懂 dp,只能生成函数了,记答案为
其中
顺势计算
然后枚举
时间复杂度
namespace solution {
const int N_ = 505, mod = 998244353;
int N, M, x; using mint = GF<int, mod>;
mint fact[N_ << 1], ifact[N_ << 1], f[N_], g[N_], h[N_], ans[N_];
mint binom(int n, int m){ return n < m ? mint(0) : fact[n] * ifact[m] * ifact[n - m]; }
void solve(){
cin >> N >> M >> x; int _ = std::max(N, M) << 1;
fact[0] = 1; for(int i = 1; i <= _; i++) fact[i] = fact[i - 1] * i;
ifact[_] = ~fact[_]; for(int i = _; i; i--) ifact[i - 1] = ifact[i] * i;
for(int S = 0; S <= N; S++){
for(int k = 0; k <= S; k++) f[S] += binom(S, k) * std::min(x, k) * ((S - k) & 1 ? mod - 1 : 1);
}
for(int n = 1; n <= N; n++){
for(int v = 1; v <= M; v++){
for(int S = 0; S <= n && 1ll * S * v <= M; S++) g[v * S] += binom(n, S) * f[S];
}
for(int v = 0; v <= M; v++) h[v] = binom(v + n - 1, v);
for(int a = 0; a <= M; a++) for(int b = 0; b <= a; b++) ans[a] += g[a - b] * h[b];
for(int a = 1; a <= M; a++) std::cout << ans[a] << ' ';
std::cout << '\n';
for(int a = 0; a <= M; a++) ans[a] = g[a] = h[a] = 0;
}
}
}
异或图
给定一张
n 个点m 条边的无向图和一个长度为n 的数组a_1, a_2, \cdots , a_n 以及一个整数C ,你需要求出有多少个长度为n 的数组b 满足:
- 对于每条边
(u, v) ,b_u \ne b_v 。答案对
998244353 取模。
首先考虑
假如存在一个
枚举
-
-
$$ \begin{aligned} &\mathrm{dp}(i,0,1)\gets\mathrm{dp}(i,0,1)2^k+\mathrm{dp}(i,0,0)\\ &\mathrm{dp}(i,1,1)\gets\mathrm{dp}(i,1,1)2^k+\mathrm{dp}(i,1,0)\\ \end{aligned} $$
最后统计
这样时间复杂度就可以做到
现在把这张无向图的信息加上来。我们发现强制钦定两个点取不同的值极其困难,但是钦定取相同的值是容易的,因此可以
对着边集容斥显然没有前途,不妨转为点集容斥,我们发现边集的限制本质上相当于给点集划分为若干个连通块,每个连通块要求填相同的数。那么不妨对连通块分开维护,考察连通块点集
如何快速求
- 我们用状压 dp,不妨用总贡献减去不连通贡献,对于一张图所有子图的总贡献就是
S 是否是独立集的判定函数g(S) (可用二项式定理证明),那么就有f(S)=g(S)-\sum_{e(S)\in T,T\subseteq S}g(S \backslash T) f(T) 。 - 连通块问题总是优先往
\ln / \exp 上套。一张图的总贡献是g(S) ,而也可以改为分成若干个连通块,每个连通块单独贡献f(S) ,因此就有f = \ln(g + 1) 。
因此可以
最后我们要枚举点集的连通块划分方法,对于每一个连通块划分方法,大小为奇数的连通块相当于上限就是连通块中点的
朴素枚举量为
将所有点按照
转移的时候枚举集合
最后会收集到一个一维数组
总时间复杂度
namespace solution {
using u64 = unsigned long long;
const int mod = 998244353, N = (1 << 15) + 5;
using mint = GF<int, mod>;
u64 a[25], C;
int n, m, e[25][25], id[25], rev[25];
mint ind[N], f[N], g[25][N];
mint crystal(std::vector<u64> a){
static mint f[25][2][2];
int n = a.size(); a.insert(a.begin(), 0ll);
mint ans = 0;
for(int k = 62; ~k; k--){
memset(f, 0, sizeof(f)), f[0][0][0] = 1;
u64 tag = 0; mint pw2 = mint(2) ^ k;
for(int i = 1; i <= n; i++){
u64 _ = ((a[i] >> k) & 1); mint c = mint::fit((a[i] % (1ull << k)) + 1);
tag ^= a[i] >> (k + 1);
for(int u : {0, 1}) for(int v : {0, 1}) f[i][u ^ _][v] += f[i - 1][u][v] * c;
if(_) for(int u : {0, 1}) f[i][u][1] += (f[i - 1][u][1] * pw2) + f[i - 1][u][0];
}
if(tag) break;
ans += f[n][0][1];
if(!k) ans += f[n][0][0];
}
return ans;
}
mint solve(std::vector<u64> a){
if(!C) return crystal(a);
a.push_back(C); mint ans = crystal(a);
a.back()--; ans -= crystal(a);
return ans;
}
void solve(){
cin >> n >> m >> C;
for(int i = 1; i <= n; i++) cin >> a[i], id[i] = i;
std::sort(id + 1, id + n + 1, [](int x, int y){ return a[x] < a[y]; });
for(int i = 1; i <= n; i++) rev[id[i]] = i;
std::sort(a + 1, a + n + 1);
for(int i = 1, u, v; i <= m; i++){
cin >> u >> v, u = rev[u], v = rev[v], e[u][v] = e[v][u] = 1;
}
for(int S = 0; S < (1 << n); S++){
bool ok = true;
for(int i = 1; i <= n; i++){
if(!(S >> (i - 1) & 1)) continue;
for(int j = i + 1; j <= n; j++){
if(!(S >> (j - 1) & 1)) continue;
if(e[i][j]){ ok = false; break; }
}
if(!ok) break;
}
ind[S] = ok;
}
for(int S = 0; S < (1 << n); S++){
f[S] = ind[S]; int min = S & -S;
for(int T = S & (S - 1); T; T = (T - 1) & S) if(T & min) f[S] -= ind[S ^ T] * f[T];
}
g[0][0] = 1;
for(int i = 1; i <= n; i++){
for(int S = 0; S < (1 << n); S++){
if(S >> (i - 1) & 1){ g[i][S ^ (1 << (i - 1))] += g[i - 1][S]; continue; }
int all = (S ^ ((1 << n) - 1)) & ((1 << n) - (1 << i));
for(int T = all; ; T = (T - 1) & all){
mint c = g[i - 1][S] * f[T | (1 << (i - 1))];
if(__builtin_popcount(T) & 1) g[i][S | T] += c * (mint::fit(a[i]) + 1);
else g[i][S | T | (1 << (i - 1))] += c;
if(!T) break;
}
}
}
mint ans = 0;
for(int S = 0; S < (1 << n); S++){
std::vector<u64> vct;
for(int i = 1; i <= n; i++) if(S >> (i - 1) & 1) vct.push_back(a[i]);
ans += solve(vct) * g[n][S];
}
std::cout << ans << '\n';
}
}
错排
我们从
尝试求出
另一种视角是,我们考虑第一个位置怎么填,如果填一个没有限制的数,那么去掉第一个位置后,我们少了一个没有限制的数,但是原本限制位置为第一位的那个元素,可以随便放了,所以少了一个有限制的数,多了一个没有限制的数,总体来看就是少了一个有限制的数,即
我们给出一个常数
这样只需要取
时间复杂度
namespace solution {
const int N = 2e5, N_ = 2e5 + 5, B = 447, B_ = 455, mod = 998244353;
using mint = GF<int, mod>;
mint fact[N_], ifact[N_], f[B_][N_];
mint binom(int n, int m){ return m < 0 || n < m ? 0 : fact[n] * ifact[m] * ifact[n - m]; }
void solve(){
fact[0] = 1; for(int i = 1; i <= N; i++) fact[i] = fact[i - 1] * i;
ifact[N] = ~fact[N]; for(int i = N; i; i--) ifact[i - 1] = ifact[i] * i;
for(int i = 0; i <= B; i++){
f[i][0] = fact[i * B], f[i][1] = fact[i * B] * i * B;
for(int j = 2; j <= N; j++) f[i][j] = f[i][j - 1] * i * B + (f[i][j - 1] + f[i][j - 2]) * (j - 1);
}
int T; cin >> T;
while(T--){
int n, m; cin >> n >> m; if(n < 2 * m){ std::cout << "0\n"; continue; }
mint ans = 0; int k = m % B;
for(int i = 0; i <= k; i++) ans += binom(k, i) * f[m / B][n - 2 * m + i];
std::cout << ans * fact[m] * binom(n - m, m) << '\n';
}
}
}
匹配
给出一个
2n 个点(n 个左部点n 个右部点)m 条边的二分图,每条边有黑白两种颜色,你需要选出一个黑色边数量为偶数的完美匹配,或指出无解。
先用 Dinic 等最大流算法随便选出一个完美匹配,如果不存在肯定无解,如果这组完美匹配恰好黑色边数量为偶数,那么直接输出这组解。
我们尝试对最大流的结果进行一次反悔来替换掉若干个匹配边,具体来说,我们在最大流的残量网络上找到一个环,那么环上肯定是被流过的边撤销流,没被流过的边添加流,dfs 找出这样的环就好了。
回响形态
给出一个长度为
n 的字符串S ,q 组询问,每组询问给出k ,你需要求出所有中心为\frac{k}{2} 的子串的 Border 个数之和。
首先求出以
我们尝试构造一个字符串
那么直接上 Manacher 即可。时间复杂度
Unpair Ampere
给出一个
n 个点m 条边的 DAG,所有入度为0 的点被分成两个集合A,B ,一个入度为0 的点转换所属的集合代价为a_u 。其余点如果既能被A 中的点到达,又能被B 中的点到达,则代价为a_u 。求出最小代价和。
奇奇怪怪的最小代价,首选最小割建模。这道题建模也很直观,划分到集合为
对于入度不为
这样我们就得到了初步思路:对于每一个点
- 对于原本位于集合
A 的点,连边(S,u,a_u) 表示改选B 的代价为a_u 。连边(u,u’,\infty),(u’,u,\infty) 表示正图和反图密不可分。 - 对于原本位于集合
B 的点,连边(u,T,a_u),(u,u’,\infty),(u’,u,\infty) 原因同上。 - 对于其他点,连边
(u,u’,a_u) 表示如果两边都能到达,可以割断这条边阻止连通。
然后就做完了。
音乐课
给定一个长度为
n 的序列h ,q 次操作,支持:
1 p v:h_p\gets v 。2 m k:构造一个长度为n 的序列a ,使得|a_i|\leq m, \sum a_i=k 。最大化\sum a_i h_i 。求这个最大值。
考虑如果是
对于本题可以放负数,简单的策略不再适用,给式子变一下形
如果用数据结构维护,我们就需要支持查询全局和,以及前
Surprise me!
给定一个长度为
n 的序列a 和一个n 个点的树,计算:\frac{1}{n(n-1)}\sum_{i=1}^n\sum_{j=1}^n \varphi(a_ia_j) \operatorname{dis}(i,j) 答案对
10^9+7 取模。
套路推式子题:
因此可以设:
因此只需要计算
前面一个很好求,后面那个双重和式,直接在虚树上做一个 dp,在
数字电路
给定一个
n+m 个点的树,其中m 个点是叶子,每个叶子有一个颜色A_u\in\{0,1\} ,每个非叶子节点u 有一个位置的权值P_u\in [0,|\text{son}(u)|] 。对于一个非叶子节点
u ,如果它的至少P_u 个儿子颜色是1 ,则A_u=1 ,否则A_u=0 ,记此时的答案为使得根节点颜色为1 的分配各点权值方案数。有
q 次修改,每次给出一个区间[l,r] ,你需要反转编号位于该区间的所有叶子的颜色,然后回答现在整棵树的答案。
设
但是直接做时间复杂度太高了,我们需要优化。注意到我们这个式子从另一个视角看,等同于每个儿子有一定概率被选择,计算选择数量除以
边界是对于叶子
而后面那个乘积式可以树上 dfs 递推预处理出来,记为
时间复杂度
白兔迷宫
在一个
n 个点m 条边的有向图上进行随机游走。从起点S 出发,每次等概率选择一条出边移动,到达终点T 时停止。图中存在两类边:
- 1 号边:经过后计数器加 1。
- 0 号边:经过后计数器清零。
计数器初始值为 0。求游走停止时,计数器的期望和方差,答案对
998244353 取模。
根据随机变量的方差公式
维护一个初始值为
0 的计数器x ,不停地投掷一枚公平六面体骰子,如果投掷到:
求终止时计数器的期望值和计数器平方的期望值。
这道题有两种做法,都能得到答案。
-
设
f_n=E[X\mid x=n],g_n=E[X^2\mid x=n] ,那么有f_n=\frac{1}{2}f_0+\frac{1}{3}f_{n+1}+\frac{1}{6}(n+1),g_n=\frac{1}{2}g_0+\frac{1}{3}g_{n+1}+\frac{1}{6}(n+1)^2 。这里有一个主流做法和一个不是那么主流的做法:- 对
f_0 不断展开,得到f_n=\sum_{k\geq 0} 3^{-k}(\frac{1}{2} f_0+\frac{1}{6} (n+k+1)) ,令n=0 就会得到一个关于f_0 的方程,然后用数列知识解这个方程即可。 - 注意到
f_n 应当是一个关于n 的一次函数(数学归纳法很容易观察出来),设f_n=an+b ,代入即可,可以解出b=\frac{3}{4}f_0+\frac{3}{8},a=\frac{1}{4} 。
类似的,我们可以观察到
g_n 应该是一个二次函数,设g_n=an^2+bn+c ,即可解出a=\frac{1}{4},b=\frac{3}{4},c=\frac{3}{4}(1+g_0) 。 - 对
-
尝试直接求
E[X] ,很容易列出E[X]=\frac{1}{2}E[X]+\frac{1}{3}(E[X]+1)+\frac{1}{6} 出来,但是这是有问题的,因为这个+1 如果遇到之后的1,2,3 可能就无法造成贡献了,所以这个1 应该换成到达抛出6 的状态后,都没有遇到1,2,3 的概率,即\frac{1}{6}\sum_{k\geq 0} 3^{-k}=\frac{1}{4} ,因此解E[X]=\frac{1}{2}E[x]+\frac{1}{3}(E[X]+\frac{1}{4})+\frac{1}{6} 就可以求得答案。对于E[X^2] ,我们也可以列出E[X^2]=\frac{1}{2}E[X^2]+\frac{1}{3}(E[X^2]+\frac{1}{4}(2E[X]+1))+\frac{1}{6} (2x+1 即(x+1)^2-x^2 ),同样解这个方程即可。
最终可以求出
OK,我们可以通过这道题的方法来求出原题。先来尝试应用上一题的第二种做法,那么我们需要下面几个东西:
这样就可以通过
特别的,
这样使用高斯消元即可解出全体
然后考虑
这样就可以列出转移:
特别地,
另外也可以使用上一题的第一种做法,可以发现从
Rainbow Balls
给定一个包含
n 种颜色小球的可重集合,第i 种颜色的小球数量为a_i 。令总球数为S = \sum a_i 。重复以下操作直至所有小球颜色相同:
- 从中依次随机抽取两个不同的小球,设先后抽出的球颜色分别为
C_1 和C_2 。- 令
C_2\gets C_1 ,然后放回这两个球。每次操作耗时
1 秒,求操作总时间的期望值。结果对10^9 + 7 取模。
本题有基于 Martingale Theory 的做法但是严重超纲,不过 zak 给出了一个更加优秀的做法。
我们枚举最后是什么颜色,设
其中
那么我们有:
根据边界
记
因此
zbo
给定一棵包含
n 个节点的加权无向树,初始时1 号点已存在城堡。现有k 个依次给出的操作,每个操作会在指定节点d_i 建造一座新城堡。设当前已建造城堡的节点集合为
S 。每次建造后,请输出\sum_{x \in S} \sum_{y \in S} \text{dis}(x, y) 。
注意到:
后面的和式应该如何维护?这也是一个经典问题,每次加上一个点