P16637 春季限定独立集问题 完全不是数据结构,拉完了
先考虑 怎么做。容易想到设 分别表示当前以 点为根,独立集中选 / 不选 点,独立集大小最大是多少。转移直接显式建树然后 DFS 合并儿子结点的 dp 信息即可。
具体的,有:
如果你做过 P8352 的话就会想到 两个状态中只保留一个状态,然后再新开一个 dp 数组维护 对应状态的差值。具体的,设 ,则 的转移形如:
很显然有结论 。因此以 为根的子树取 作为其 dp 状态严格优于取 作为其 dp 状态,当且仅当 ,而 当且仅当 。考虑记 ,则有 当且仅当 。
此时还可以发现:
记 ,则转移式可以被写作:
最终的答案即为 。简单分析可知 ,即树的最大独立集大小为满足 的 的数量。
此时考虑对于一个点 ,其儿子结点 与其的关系。显然有 。继续挖掘性质后还可以发现记 ,则必有 为质数且 ,其中 表示 的最小质因数。当然还必须满足 ,因此记 表示剩余倍率上限为 时最小的能走到一个满足 的 的质数的值。特殊的,若不存在这样的一个质数,则记 。
则此时 点满足 当且仅当 。证明可以考虑 一直乘正整数, 在过程中是单调不增的。
因此最终问题的答案可以被表示为:。
考虑处理出 的值。显然有 ,对 有 。进一步优化可以发现,若 部分没有找到满足条件的 ,则只需要判断比 要大的最小的质数是否满足条件即可。证明考虑若 则必有 ,从而可以推出 ,显然不可能有 。
此时考虑从 点走到其某个儿子结点 ,若 ,则新的倍率上限变为 。根据下取整的一些性质可知不同的倍率上限只有 个,因此考虑先处理所有可能得到的倍率上限然后从小到大递推这些 的值即可。
考虑固定整数 ,每一轮处理所有满足 的 对答案的贡献。显然对答案有贡献的 的数量是 级别的,且可能对答案有贡献的 所在的区间为 ,每一次只需要在这个区间内统计有多少个 满足 即可。此时 的值也是固定的,因此问题变为在给定区间内求有多少 的 严格小于某定值 。
记 表示 的所有正整数 中,有多少个 满足 。换句话说,若严格小于 的质数有 ,则 的值即为有多少个 的正整数 满足 至少被这些质数中的一个整除。同理记 为 在 上的补集,即 表示 的所有正整数 中,有多少个 满足 不能被前 小的质数中的任何一个整除。则 和 之间显然有等量关系 。其中 表示 中质数的数量。
假设目前已经处理出了 的值,则固定整数 对答案的贡献就可以表示为 。把所有这样的贡献累加即可得到最终答案。
问题在于快速计算 的值。 是难以计算的,考虑通过其补集 来辅助计算。这里我们有递推公式:
其中 为第 小的质数。边界条件显然为 。
大体解释一下上面的递推公式:在 的整数里先数出来不能被前 小的质数整除的数的数量,然后再额外减掉不能被 整除但是能被前 小的质数整除的数的数量。这些数都可以表示为 的形式(),而因为质数两两互质所以 也同样不能被前 小的质数整除,这样的数的数量即为 。
直接模拟上述流程单次递归计算 的时间复杂度接近 ,考虑对该部分优化。若递归处理到某一层时,其满足 则此时可以直接计算 (满足条件的数只有 和严格大于 的质数)。直接计算然后返回即可。此时单次计算 的时间复杂度上界为 ,再做一些杂七杂八的剪枝后可以通过。
inline int power(int a, int b, int c) {
a %= c;
int res = 1;
while (b) {
if (b & 1) res = (u128)res * a % c;
a = (u128)a * a % c, b >>= 1;
} return res;
}
inline int inversion(int x, int Mod = mod) { return power(x, Mod - 2, Mod); }
int pr[N], pi_pre[N], isp[N], idx;
inline void sieve(int lim) {
for (int i = 2; i <= lim; ++i) {
if (!isp[i]) pr[++idx] = i;
for (int j = 1; j <= idx && i * pr[j] <= lim; ++j) {
isp[i * pr[j]] = 1;
if (i % pr[j] == 0) break;
}
}
for (int i = 0; i <= lim; ++i) {
if (i) pi_pre[i] = pi_pre[i - 1];
if (i >= 2 && !isp[i]) ++pi_pre[i];
}
}
const int prime_list[] = {2, 7, 61};
inline bool miller_rabin(const int n, int a) {
register int d = n - 1, r = 0;
while (~d & 1) d >>= 1, ++r;
register int x = power(a, d, n);
if (x == 1) return 1;
for (register int i = 0; i < r; ++i) {
if (x == n - 1) return 1;
x = (u128)x * x % n;
} return 0;
}
inline bool isprime(const int n) {
if (n < 4) return n > 1;
if (n == prime_list[0] || n == prime_list[1] || n == prime_list[2]) return 1;
for (int i = 0; i < 3; ++i) if (n % prime_list[i] == 0 || !miller_rabin(n, prime_list[i])) return 0;
return 1;
}
inline int getnext(int x) {
int y = x / 2 + 1;
if (y < 3) return 2;
if (~y & 1) ++y;
while (!isprime(y)) y += 2;
return y;
}
inline int isqrt(int x) {
int r = sqrt(x);
while ((r + 1) * (u128)(r + 1) <= (u128)x) ++r;
while ((u128)r * r > (u128)x) --r;
return r;
}
inline int icbrt(int x) {
int r = cbrt(x);
while ((r + 1) * (u128)(r + 1) * (r + 1) <= (u128)x) ++r;
while ((u128)r * r * r > (u128)x) --r;
return r;
}
inline int issqrt(int x) {
int r = sqrt(sqrt(x));
while ((r + 1) * (u128)(r + 1) * (r + 1) * (r + 1) <= (u128)x) ++r;
while ((u128)r * r * r * r > (u128)x) --r;
return r;
}
int prephi[30031];
inline int phi_base(int u) { return u / 30030 * prephi[30030] + prephi[u % 30030]; }
inline int phi_small(int u, int k) {
if (!k) return u;
if (k == 6) return phi_base(u);
return phi_small(u, k - 1) - phi_small(u / pr[k], k - 1);
}
struct myhash {
static uint64_t splitmix64(uint64_t x) {
x += 0x9e3779b97f4a7c15;
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
return x ^ (x >> 31);
}
size_t operator()(uint64_t x) const {
static const uint64_t FIXED_RANDOM = chrono::steady_clock::now().time_since_epoch().count();
return splitmix64(x + FIXED_RANDOM);
}
};
unordered_map<ull, int, myhash> pi_;
inline int prime_pi(int);
inline int Phi(int u, int k) {
if (u < 1) return 0;
if (k <= 6) return phi_small(u, k);
if (pr[k] * pr[k] > u) return prime_pi(u) - k + 1;
return Phi(u, k - 1) - Phi(u / pr[k], k - 1);
}
inline int prime_pi(int u) {
if (u <= 1000000) return pi_pre[u];
auto it = pi_.find(u);
if (it != pi_.end()) return it->second;
int a = prime_pi(issqrt(u)), b = prime_pi(isqrt(u)), c = prime_pi(icbrt(u));
int res = Phi(u, a) + (b - a + 1) * (b + a - 2) / 2;
for (int i = a + 1; i <= b; ++i) {
res -= prime_pi(u / pr[i]);
if (i <= c) {
int lim = prime_pi(isqrt(u / pr[i]));
for (int j = i; j <= lim; ++j) res -= prime_pi(u / pr[i] / pr[j]) - (j - 1);
}
} return pi_[u] = res;
}
inline int spf_count(int x, int q) {
if (x < 1 || q < 3) return 0;
if (q >= inf / 2) return x;
if (q > x) return x - 1;
int a = prime_pi(q - 1);
if ((u128)q * q > x) return x - prime_pi(x) - 1 + a;
return x - Phi(x, a);
}
inline void init() {
}
int q[N];
inline void sol() {
int n; cin >> n;
if (n == 1) { cout << 1 << '\n'; return; }
sieve(1000000);
for (int i = 1; i <= 30030; ++i) {
int ok = 1;
for (int j = 1; j <= 6; ++j)
if (i % pr[j] == 0) { ok = 0; break; }
prephi[i] = prephi[i - 1] + ok;
}
vector<int> vec;
for (int l = 1, r; l <= n; l = r + 1)
r = n / (n / l), vec.push_back(n / l);
reverse(vec.begin(), vec.end());
unordered_map<int, int, myhash> id;
for (int i = 0; i < vec.size(); ++i) id[vec[i]] = i;
memset(q, 0x3f, sizeof q);
q[0] = inf;
for (int i = 1; i < vec.size(); ++i) {
int lim = isqrt(vec[i]), ans = inf;
for (int j = 1; j <= idx && pr[j] <= lim; ++j)
if (pr[j] < q[id[vec[i] / pr[j]]]) { ans = pr[j]; break; }
if (ans == inf) ans = getnext(vec[i]);
q[i] = ans;
}
int res = 0;
for (int i = 0; i < vec.size(); ++i)
res += spf_count(n / vec[i], q[i]) - spf_count(n / (vec[i] + 1), q[i]);
cout << res << '\n';
}
喜欢的话,留下你的评论吧~