清华集训2014
玛里苟斯
statement
\(S\) 是一个可重集合,\(S = \{a_1, a_2, \dots, a_n \}\)。
等概率随机取 \(S\) 的一个子集 \(A = \{a_{i_1}, \dots, a_{i_m}\}\)。
计算出 \(A\) 中所有元素异或 \(x\), 求 \(x^k\) 的期望。
output
如果结果是整数,直接输出。如果结果是小数(显然这个小数是有限的),输出精确值(末尾不加多余的 \(0\))。
constraints
\(1 \leq n \leq 100000\),\(1 \leq k \leq 5\),\(a_i \geq 0\)。最终答案小于 \(2^{63}\) 。\(k = 1,2,3,4,5\) 各自占用 20% 的数据。
sol
我们想对每个数拆位,并将 \(k\) 次方通过枚举拆掉。
可得如下推导:
我们好奇一个事情,本题输出方式很奇怪,没有取模也没有保留小数,通过 \(O(n2 ^ n)\) 暴力我们发现答案似乎总是整数或小数部分是 \(0.5\)。
我们固定一组 \(i\),然后继续观察:
如果我们令 \(b_x = \sum\limits_{j = 1} ^ {k} 2 ^ {j - 1} (a_x \operatorname{and} 2 ^ {i_j})\),则上式为:
翻译以下是问序列 \(b_n\) 有多少个子集的异或和为 \(2 ^ k - 1\),且 \(b_x \in \{0, \cdots, 2 ^ k - 1\}\)。
可以通过线性基解决。
线性基
直接的定义可以说是异或高斯消元的结果,就是上三角矩阵。
如果使用异或高斯约旦消元,会得到一条对角线。
但是我们可以简单地动态插入一行维护:
bool insert(T x) { for (int i = W - 1; i >= 0; i--) { if (x >> i & 1) { if (!num[i]) { num[i] = x; rank++; return 1; } x ^= num[i]; } } return 0; }考虑如下问题:
求序列 \(a_n\) 有多少个子集异或和为 \(x\)。
把 \(a_i\) 都插入线性基,然后判断 \(x\) 是否与 \(a_n\) 线性相关。
- 若线性无关,答案为 \(0\)。
- 若线性相关,在基中存在唯一的方法异或出 \(x\),答案为 \(2 ^ {n - rank}\)。
回到原问题即:
时间复杂度 \(O(nk64 ^ k)\)。
优化
首先注意到复杂度里的 \(64\) 其实是可能达不到的,因为答案小于 \(2 ^ {63}\),所以值域很大会导致答案极易爆范围,记最大位数 \(w\),时间复杂度 \(O(nkw ^ k)\)。
对于 \(x, y \in a\),序列 \((a / \{x, y\}) \cup \{x, x \oplus y\}\) 的子集异或值的总体没有改变,所以每次对 \(n\) 个数求线性基是不必要的,只需在 \(n\) 个数的线性基上求线性基即可,时间复杂度 \(O(kw ^ {k + 1})\)。
可以通过。
也可以对 \(k = 1, 2\) 跑上述枚举,\(k = 3, 4, 5\) 跑 \(O(w2 ^ w)\) 暴力。
实现
#include <bits/stdc++.h>
using namespace std;
using ull = unsigned long long;
template<class T, const int W>
struct Basis {
int rank;
array<T, W> num;
Basis() : rank{}, num{} {}
bool insert(T x) {
for (int i = W - 1; i >= 0 && x; i--) {
if (x >> i & 1) {
if (!num[i]) {
num[i] = x;
rank++;
return 1;
}
x ^= num[i];
}
}
return 0;
}
};
constexpr int N = 1e5 + 5;
int n, k, num[7], lim;
ull a[N], ans;
bool flag;
void dfs(int x) {
if (x == k + 1) {
Basis<int, 5> basis;
int sum = 0;
for (int i = 1; i <= n; i++) {
int v = 0;
for (int j = 1; j <= k; j++) {
v = v * 2 + (a[i] >> num[j] & 1);
}
basis.insert(v);
}
if (!basis.insert((1 << k) - 1)) {
int sum = 0;
for (int i = 1; i <= k; i++) {
sum += num[i];
}
sum -= basis.rank;
if (sum == -1) {
ans += flag;
flag ^= 1;
} else {
ans += 1ull << sum;
}
}
return;
}
for (int i = 0; i <= lim; i++) {
num[x] = i;
dfs(x + 1);
}
}
int main() {
cin >> n >> k;
Basis<ull, 64> basis;
for (int i = 1; i <= n; i++) {
cin >> a[i];
basis.insert(a[i]);
}
n = 0;
for (int i = 0; i <= 63; i++) {
if (basis.num[i]) {
a[++n] = basis.num[i];
lim = i;
}
}
dfs(1);
cout << ans;
if (flag) {
cout << ".5";
}
cout << "\n";
return 0;
}
主旋律
statement
\(n\) 个点 \(m\) 条边的简单有向图,求有多少边的子集删去之后整个图仍然强联通。
constraints
对于 100% 的数据满足: \(n \leq 15, 0 \leq m \leq n(n − 1)\)。
sol
生成函数
注意到强连通分量如果连成 DAG 则就是简单有向图,说明如果 DAG 能用符号化方法说明,那么只需要求 \(\ln\text{简单有向图}\)。
留下关于这部分 GF 理论的一篇 paper。
DP
本问题是明显的状压,但研究一般的计数得到的 DP 是类似的,不妨从另一些问题入手。
\(n\) 个点形成多少个有标号 DAG。
设 \(f_i\) 等于 \(n\) 个点的有标号 DAG 数。
枚举 \(j\) 个入度为 \(0\) 的点
实际上算出来的有问题,因为要保证剩下 \(i - j\) 都有入度。
我们尝试使用容斥,引入容斥系数 \(g_i\)。
那么对于一个有 \(i\) 个源点的 DAG,会满足
令 \(g_i = (-1) ^ {i + 1}\),容易验证上式成立。
综上所述,可以得到一个 \(O(n ^ 2)\) 的 DP
\(n\) 个点形成多少个弱连通有标号 DAG。
注意到本质是对上一个问题求 \(\ln\)。
设 \(f_i\) 等于 \(n\) 个点的有标号 DAG 数。
设 \(g_i\) 等于 \(n\) 个点的弱连通有标号 DAG 数。
则
容斥计算强连通分量数 \(\ge 2\) 的有标号 DAG 数量。
设 \(h_i\) 等于 \(n\) 个点的有标号有向图数。
枚举 \(1\) 号点所在连通块大小
综上所述,可以得到一个 \(O(n ^ 2)\) 的 DP。
\(n\) 个点形成多少个有标号 SCC。
瓶颈在于容斥系数与 DAG 的 SCC 数量挂钩,所以我们对容斥系数进行 DP。
设 \(f_i\) 等于 \(n\) 个点的有标号 SCC 数。
设 \(g_i\) 等于 \(n\) 个点的有标号有向图的容斥系数和,即缩点后奇数个 SCC 算 \(+1\),偶数个 SCC 算 \(-1\)。
注意 \(f\) 和 \(g\) 之间类似 \(\ln, \exp\) 的关系,枚举 \(1\) 号点所在连通块大小,并分离 SCC 大小为 \(1\) 的 DAG。
设 \(h_i\) 等于 \(n\) 个点的有标号有向图数。
根据之前的容斥,枚举有向图缩点后 DAG 的源点
分离右边的 \(g_i\)
这样可以 \(O(n^2)\) DP 出 \(g_i\)。
观察 \((*)\) 式,可以求出 \(f\)
综上所述,可以得到一个 \(O(n ^ 2)\) 的 DP。
回到原问题,我们稍微改一改即可。
设 \(f_s\) 表示 \(s\) 的导出子图的强连通子图数。
设 \(g_s\) 表示 \(s\) 的导出子图的子图容斥系数和,即缩点后奇数个 SCC 算 \(+1\),偶数个 SCC 算 \(-1\)。
设 \(h_s\) 表示 \(s\) 的导出子图的子图数。
令 \(\operatorname{cross}(s, t)\) 表示 \(s\) 连向 \(t\) 的边数。
则
这和上面一个问题几乎是一样的。
使用 bitset 处理集合的入边和出边,可以快速计算出 \(\operatorname{cross}(t, s / t)\) 的值。
时间复杂度 \(O(m2 ^ n + \dfrac{m3 ^ n}{\omega})\),可以通过。
实现
#include <bits/stdc++.h>
using namespace std;
constexpr int mod = 1e9 + 7;
int add(int a, int b) {
return a += b, a < mod ? a : a - mod;
}
int sub(int a, int b) {
return a -= b, a >= 0 ? a : a + mod;
}
int mul(int a, int b) {
return 1ll * a * b % mod;
}
int inv(int a, int b = mod) {
return a == 1 ? 1 : b - inv(b % a, a) * b / a;
}
constexpr int S = 1 << 15;
int n, m, pow2[211];
vector<pair<int, int>> E;
int f[S], g[S], h[S];
bitset<210> in[S], out[S];
int cross(int s, int t) {
return (out[s] & in[t]).count();
}
int main() {
cin >> n >> m;
pow2[0] = 1;
for (int i = 1; i <= m; i++) {
pow2[i] = mul(pow2[i - 1], 2);
}
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
u--, v--;
E.emplace_back(u, v);
}
for (int s = 0; s < 1 << n; s++) {
for (int i = 0; i < m; i++) {
auto [u, v] = E[i];
if (s >> u & 1 && s >> v & 1) {
h[s]++;
}
if (~s >> u & 1 && s >> v & 1) {
in[s][i] = 1;
}
if (s >> u & 1 && ~s >> v & 1) {
out[s][i] = 1;
}
}
h[s] = pow2[h[s]];
}
for (int s = 0; s < 1 << n; s++) {
g[s] = h[s];
for (int t = s - 1 & s; t; t = t - 1 & s) {
g[s] = sub(g[s], mul(mul(g[t], h[s ^ t]), pow2[cross(t, s ^ t)]));
}
f[s] = g[s];
for (int t = s - 1 & s; t; t = t - 1 & s) {
if (t & 1) {
f[s] = add(f[s], mul(f[t], g[s ^ t]));
}
}
}
cout << f[(1 << n) - 1] << "\n";
return 0;
}
奇数国
statement
序列 \(a\) 有 \(n\) 个数,初始为 \(3\)。
\(q\) 次操作,有两种:
- 求 \(\varphi\left( \prod\limits_{i = l} ^ r a_i \right)\)。
- \(a_x \leftarrow y\)。
保证序列所有数的质因子始终只可能是最小的 \(60\) 个素数 \((2, 3, 5, \cdots, 281)\)。
constraints
对于 100% 的数据满足: \(n, q \leq 10 ^ 5, a_i, y \le 10 ^ 6\)。
sol
因为
所以维护乘积和每个素数的出现情况即可,维护后者可以压位。
时间复杂度 \(O(n\log n)\)。
实现
#pragma GCC optimize("Ofast", "inline")
#include <bits/stdc++.h>
#define ls p << 1
#define rs ls | 1
#define mid ((l + r) >> 1)
using namespace std;
struct IO {
template<class T>
IO &operator>>(T &x) {
x = 0;
int f = 1;
char c;
while (!isdigit(c = getchar())) {
if (c == '-') {
f = -1;
}
}
do {
x = x * 10 + c - '0';
} while (isdigit(c = getchar()));
x *= f;
return *this;
}
IO &operator>>(char &x) {
while (!isalpha(x = getchar()));
return *this;
}
template<class T>
IO &operator<<(T x) {
static int s[130], t = 0;
if (x < 0) {
x = -x;
putchar('-');
}
do {
s[++t] = x % 10;
} while (x /= 10);
while (t) {
putchar('0' + s[t--]);
}
return *this;
}
IO &operator<<(char x) {
putchar(x);
return *this;
}
} io;
constexpr int mod = 19961993;
int add(int a, int b, int P = mod) {
a += b;
if (a >= P) {
a -= P;
}
return a;
}
int sub(int a, int b, int P = mod) {
a -= b;
if (a < 0) {
a += P;
}
return a;
}
int mul(int a, int b, int P = mod) {
return 1ll * a * b % P;
}
int inv(int a, int P = mod) {
if (a == 1) {
return 1;
} else {
return P - 1ll * inv(P % a, a) * P / a;
}
}
int divi(int a, int b, int P = mod) {
return mul(a, inv(b, P), P);
}
int qpow(int a, int b, int P = mod) {
int c = 1;
while (b) {
if (b & 1) {
c = mul(c, a, P);
}
a = mul(a, a, P);
b >>= 1;
}
return c;
}
constexpr int N = 1e5 + 5, M = N * 4;
constexpr int pri[61] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281};
int n = 1e5, q, a[N];
int v[M];
bitset<61> exist[M];
void pull(int p) {
v[p] = mul(v[ls], v[rs]);
exist[p] = exist[ls] | exist[rs];
}
void build(int p = 1, int l = 1, int r = n) {
if (l == r) {
v[p] = a[l] % mod;
for (int i = 1; i <= 60; i++) {
exist[p][i] = a[l] % pri[i] == 0;
}
return;
}
build(ls, l, mid);
build(rs, mid + 1, r);
pull(p);
}
void change(int x, int y, int p = 1, int l = 1, int r = n) {
if (l == r) {
a[l] = y;
v[p] = a[l] % mod;
for (int i = 1; i <= 60; i++) {
exist[p][i] = a[l] % pri[i] == 0;
}
return;
}
if (x <= mid) {
change(x, y, ls, l, mid);
} else {
change(x, y, rs, mid + 1, r);
}
pull(p);
}
int askv(int x, int y, int p = 1, int l = 1, int r = n) {
if (x <= l && r <= y) {
return v[p];
}
int ans = 1;
if (x <= mid) {
ans = mul(ans, askv(x, y, ls, l, mid));
}
if (mid < y) {
ans = mul(ans, askv(x, y, rs, mid + 1, r));
}
return ans;
}
bitset<61> askexist(int x, int y, int p = 1, int l = 1, int r = n) {
if (x <= l && r <= y) {
return exist[p];
}
bitset<61> ans;
if (x <= mid) {
ans |= askexist(x, y, ls, l, mid);
}
if (mid < y) {
ans |= askexist(x, y, rs, mid + 1, r);
}
return ans;
}
int main() {
for (int i = 1; i <= n; i++) {
a[i] = 3;
}
build();
io >> q;
while (q--) {
int op, x, y;
io >> op >> x >> y;
if (op == 0) {
int v = askv(x, y);
auto exist = askexist(x, y);
for (int i = 1; i <= 60; i++) {
if (exist[i]) {
v = mul(v, pri[i] - 1);
v = divi(v, pri[i]);
}
}
io << v << '\n';
} else {
change(x, y);
}
}
return 0;
}

浙公网安备 33010602011771号