CF #580 部分题解
CF1205D Almost All
考虑一条链我们怎么做。一个简单的方式是取中点为根,不妨假设 \(n\) 为奇数,那么左右两边的点数均为 \(\lfloor \frac{n}{2} \rfloor\)。类似 BSGS 的构造,左边每条边权值均为 \(1\),右边每条边权值均为 \(\lfloor \frac{n}{2} \rfloor + 1\),这样可以表示 \(1 \sim (\lfloor \frac{n}{2} \rfloor+1) ^2-1 = \lfloor \frac{n}{2} \rfloor(\lfloor \frac{n}{2} \rfloor + 2) > \lfloor \frac{2n^2}{9}\rfloor\)。
稍微推广一下这个做法,首先对于一颗 \(n\) 个点的树,容易根据 dfs 序构造使得根到每个点的距离取遍 \(1 \sim n-1\)。考虑选择某个点为根,将儿子分成两个集合,设两个集合大小分别为 \(p,q\),第一个集合构造 \(1 \sim p\),第二个集合构造 \((p+1),2(p+1),\cdots,q(p+1)\),这样可以凑出 \(1 \sim (p+1)(q+1) - 1\)。于是我们现在的目标是找到一组合适的 \(p,q\) 满足 \((p+1)(q+1)-1 \geq \lfloor \frac{2n^2}{9}\rfloor\)。
容易发现 \(p,q\) 的值越接近 \((p+1)(q+1)-1\) 越大。并且注意到 \(\lfloor \frac{2n^2}{9}\rfloor = \lfloor \frac{2n}{3} \times \frac{n}{3}\rfloor\),这提示我们取重心(如果你不知道为什么,不妨往下看并把它当做一个套路)。这时所有子树的大小不大于 \(\lfloor \frac{n}{2}\rfloor\)。我们只需要将这些子树从小到大排序,选择一段前缀使得其恰好大于等于 \(\lfloor \frac{n}{3}\rfloor\) 即可,可以证明这些子树的大小之和不超过 \(2 \lfloor \frac{n}{3}\rfloor\)。容易证明构造的合法性,于是直接做就行了。
Code
/*
也许所有的执念 就像四季的更迭
没有因缘 不需致歉
是否拥抱着告别 就更能读懂人间
还是感慨 更多一点
*/
#include <bits/stdc++.h>
#define pii pair<int, int>
#define mp(x, y) make_pair(x, y)
#define pb push_back
#define fi first
#define se second
#define int long long
#define mem(x, v) memset(x, v, sizeof(x))
#define mcpy(x, y, n) memcpy(x, y, sizeof(int) * (n))
#define lob lower_bound
#define upb upper_bound
using namespace std;
inline int read() {
int x = 0, w = 1;char ch = getchar();
while (ch > '9' || ch < '0') { if (ch == '-')w = -1;ch = getchar(); }
while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
return x * w;
}
inline int min(int x, int y) { return x < y ? x : y; }
inline int max(int x, int y) { return x > y ? x : y; }
const int MN = 1e3 + 5;
const int Mod = 1e9;
const int Inf = 2e18;
inline void Add(int &x, int y) { x += y; if (x >= Mod) x -= Mod; }
inline void Dec(int &x, int y) { x -= y; if (x < 0) x += Mod; }
inline int qPow(int a, int b = Mod - 2, int ret = 1) {
while (b) {
if (b & 1) ret = ret * a % Mod;
a = a * a % Mod, b >>= 1;
}
return ret;
}
// #define dbg
int N;
vector <int> G[MN];
int p, sz[MN], mx[MN], fa[MN];
int vr[MN], bak;
inline void DFS0(int u, int pr, int Ty) {
sz[u] = 1;
for (int v : G[u]) if (v != pr)
DFS0(v, u, Ty), sz[u] += sz[v], mx[u] = max(mx[u], sz[v]);
mx[u] = max(mx[u], N - sz[u]);
if (!Ty && (!p || mx[p] > mx[u])) p = u;
}
inline int cmp(int i, int j) {
return sz[i] < sz[j];
}
int dfn[MN], dfc;
inline void DFS1(int u, int pr, int val) {
fa[u] = pr, dfn[u] = (++dfc) * val;
for (int v : G[u]) if (v != pr) DFS1(v, u, val);
}
signed main(void) {
N = read();
for (int i = 1, u, v; i < N; i++) u = read(), v = read(), G[u].pb(v), G[v].pb(u);
DFS0(1, 0, 0), DFS0(p, 0, 1);
for (int v : G[p]) vr[++bak] = v;
sort(vr + 1, vr + bak + 1, cmp);
int sum = 0, da;
for (int i = 1; i <= bak; i++) {
sum += sz[vr[i]], DFS1(vr[i], p, 1);
if (sum >= N / 3) { da = i; break; }
}
dfc = 0;
for (int i = da + 1; i <= bak; i++) DFS1(vr[i], p, sum + 1);
for (int i = 1; i <= N; i++) if (i != p) printf("%lld %lld %lld\n", i, fa[i], dfn[i] - dfn[fa[i]]);
return 0;
}
CF1205E Expected Value Again
神题。首先,一个字符串 \(s\) 有长度为 \(i\) 的 border,那么其具有长度为 \(|s| - i\) 的 period,于是可以转化为求 period 的期望个数。
考虑 \(f(s)^2\),可以考虑一个经典套路:枚举 \(1 \leq i,j < n\) 使得 \(i,j\) 同时是 \(s\) 的 period,那么 \(i,j\) 的对数就是 \(f(s)^2\),于是可以考虑算每一对 \(i,j\) 的贡献。也就是对于每一对 \(i,j\),计算出有多少长度为 \(n\) 的字符串 \(s\) 满足 \(s\) 同时存在 \(i,j\) 两个 period。
若一个字符串同时存在 \(p,q\) 两个 period,我们对每个点 \(i\) 向 \(i+p\) 和 \(i + q\) 连边表示这些位置上的字符相等,若最终有 \(c\) 个等价类,那么方案数即为 \(k^c\)。现在考虑如何快速计算等价类的个数。
先解决 \(p \perp q\) 的情况,此时 \(i \to i + p\) 的边使得等价类的个数不会超过 \(p\) 个,并且 \(1 \sim p\) 在不同的等价类中。
-
若 \(p + q \leq n\),此时每个等价类 \(i \in [1,p]\) 都有出边 \(i \to (i + p) \ \mathrm{mod} \ q\),由于 \(p \perp q\),这些等价类恰好连成一个大环。
-
若 \(p + q > n\),不妨设 \(p < q\),那么此时只有 \(1 \sim n - q\) 是有出边的,\(n - q + 1 \sim p\) 没有出边。完整的图是一个环,现在断掉了若干条边,则是一些链的集合。那么等价类的个数为点数减去边数,即 \(p-(n-q)=p+q-n\)。
否则,设 \((p,q) = d\),那么模 \(d\) 不等的位置互不影响,因此我们可以把问题转化成 \(d\) 个 \(p \perp q\) 的子问题。
-
若 \(p + q \leq n\),则等价类个数为 \(d\)。
-
若 \(p + q > n\),我们记 \((n,p,q)\) 为一个子问题,那么问题可以转化为 \(n \ \mathrm{mod} \ d\) 个 \((\lfloor \frac{n}{d} \rfloor + 1,\frac{p}{d},\frac{q}{d})\) 和 \(d - n \ \mathrm{mod} \ d\) 个 \((\lfloor \frac{n}{d} \rfloor,\frac{p}{d},\frac{q}{d})\)。若 \(n < p+q-d\),那么 \(\lfloor \frac{n}{d} \rfloor + 1 < \lfloor \frac{p+q-d}{d} \rfloor + 1 = \lfloor \frac{p+q}{d} \rfloor \leq \frac{p}{d} + \frac{q}{d}\)。此时每个子问题都是 \(2\) 类,总贡献为 \(p+q-n\)。否则,子问题 \((\lfloor \frac{n}{d} \rfloor + 1,\frac{p}{d},\frac{q}{d})\) 为 \(1\) 类,贡献为 \(1\),子问题 \((\lfloor \frac{n}{d} \rfloor,\frac{p}{d},\frac{q}{d})\) 为 \(2\) 类,贡献为 \(\frac{p}{d} + \frac{q}{d} - \lfloor \frac{n}{d} \rfloor = 1\)。因此总贡献为 \(d\)。
综上,等价类个数可以写成统一的形式 \(\max\{(p,q), p+q-n\}\)。则有:
考虑容斥,先假设所有 \(i,j\) 均取 \(i+j-n\),那么此时贡献为:
枚举 \(i+j=c\),要使 \(i+j-n\) 有意义那么必有 \(c>n\)。考虑 \(i+j=c\) 的 \(i,j\) 组数,由于 \(i \in [1,n-1]\),则 \(j \in [c-n+1,c-1]\),但 \(j \in [1,n-1]\),因此 \(j \in [c-n+1,c-1] \cap [1,n-1]\)。由于 \(c>n\),那么 \(c-1>n-1,c-n+1>1\),则 \([c-n+1,c-1] \cap [1,n-1] = [c-n+1,n-1]\),则合法的 \(i,j\) 组数为 \((n-1)-(c-n+1)+1 = 2n - c - 1\)。则 \(k^{c}\) 的系数为 \(2n-(c+n)-1=n-c-1\)。
然后我们对答案进行修正:
利用分配率把减号拆开,对于前半部分:
预处理 \(\varphi\) 的前缀和即可 \(O(n)\) 计算。对于后半部分:
只考虑 \(sd>n\) 的情况。当 \(d \nmid n\) 时 \(s \geq \lfloor \frac{n-1}{d} \rfloor + 1\),但 \(2 \leq s \leq \lfloor \frac{n-1}{d} \rfloor + 1\),因此 \(s = \lfloor \frac{n-1}{d} \rfloor + 1\)。则上式可变为 \(\sum_{d=1}^{n-1} \varphi(\lfloor \frac{n-1}{d}\rfloor + 1) k^{(\lfloor \frac{n-1}{d}\rfloor + 1)d-n}\),可以 \(O(n)\) 计算。当 \(d \mid n\) 时,\(s > \frac{n}{d} = \lfloor \frac{n-1}{d} \rfloor + 1\),此时 \(s\) 不存在。但我们仍可以按照上式计算,因为这时会贡献到 \((\lfloor \frac{n-1}{d} \rfloor + 1)d - n =0\) 次项的系数上,但我们最后统计答案的时候并不会统计 \(0\) 次项,所以对答案没有影响。综上,总时间复杂度为 \(O(n)\)。
Code
/*
也许所有的执念 就像四季的更迭
没有因缘 不需致歉
是否拥抱着告别 就更能读懂人间
还是感慨 更多一点
*/
#include <bits/stdc++.h>
#define pii pair<int, int>
#define mp(x, y) make_pair(x, y)
#define pb push_back
#define fi first
#define se second
#define int long long
#define mem(x, v) memset(x, v, sizeof(x))
#define mcpy(x, y, n) memcpy(x, y, sizeof(int) * (n))
#define lob lower_bound
#define upb upper_bound
using namespace std;
inline int read() {
int x = 0, w = 1;char ch = getchar();
while (ch > '9' || ch < '0') { if (ch == '-')w = -1;ch = getchar(); }
while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
return x * w;
}
inline int min(int x, int y) { return x < y ? x : y; }
inline int max(int x, int y) { return x > y ? x : y; }
const int MN = 2e5 + 5;
const int Mod = 1e9 + 7;
const int Inf = 2e18;
inline void Add(int &x, int y) { x += y; if (x >= Mod) x -= Mod; }
inline void Dec(int &x, int y) { x -= y; if (x < 0) x += Mod; }
inline int qPow(int a, int b = Mod - 2, int ret = 1) {
while (b) {
if (b & 1) ret = ret * a % Mod;
a = a * a % Mod, b >>= 1;
}
return ret;
}
// #define dbg
int N, M, Ans;
int pr[MN], cnt, isp[MN], phi[MN], s[MN], c[MN];
inline void Init() {
phi[1] = 1;
for (int i = 2; i <= N; i++) {
if (!phi[i]) pr[++cnt] = i, phi[i] = i - 1;
for (int j = 1; j <= cnt && i * pr[j] <= N; j++) {
if (i % pr[j] == 0) { phi[i * pr[j]] = phi[i] * pr[j]; break; }
phi[i * pr[j]] = phi[i] * (pr[j] - 1);
}
}
}
signed main(void) {
N = read(), M = read();
Init();
for (int i = 2; i <= N; i++) s[i] = (s[i - 1] + phi[i]) % Mod;
for (int i = 1; i < N - 1; i++) c[i] = N - i - 1;
for (int i = 1, d; i < N; i++)
d = (N - 1) / i, Add(c[i], s[d + 1]), Dec(c[(d + 1) * i - N], phi[d + 1]);
for (int i = 1, k = M; i < N; i++, k = k * M % Mod) Add(Ans, c[i] * k % Mod);
printf("%lld\n", Ans * qPow(M, Mod - 1 - N) % Mod);
return 0;
}
CF1205F Beauty of a Permutation
神题,我狂暴拟合题解。此题要我们构造一个析合树,我们不妨先分析下析合树所具有的一些性质。以下记 \(C_x = \frac{x(x+1)}{2}\)。
引理 \(1\):析合树的合点儿子个数至少为 \(2\),析点儿子个数至少为 \(4\)。
证明:关于合点的性质比较显然,因为只有一个儿子的点没有任何意义。析点的性质可以考虑反正,若析点有 \(2\) 个儿子,那么它一定是合点,不满足定义。如果它有 \(3\) 个儿子,那么第 \(2\) 个儿子一定和第 \(1\) 或第 \(3\) 个儿子相邻,这不满足定义。这就证明了结论。根据定义我们还容易得出关于析点和合点性质的第二个结论:
引理 \(2\):一个合点贡献的连续段个数为 \(C_k\),一个析点贡献的连续段个数为 \(k+1\),其中 \(k\) 为儿子个数。
但可能的析合树仍然有很多种,我们尝试规定一种建树的方式,使得所有可能的析合树都能够与其建立映射。定义一颗析合树是好的,当且仅当其所有非叶子节点都呈现如下形态:只有第一个儿子可能为非叶节点,其余儿子均为叶子节点,我们称具有这样的形态的节点为好节点。
容易发现,每个节点对连续段数量的贡献只与其儿子数量有关,因此对于一颗不好的析合树,我们容易通过如下操作使得它变为一颗好树:找到最深的不好的节点,选择第一个儿子以外的一个非叶节点与第一个儿子子树中最左的叶子交换,重复操作直到该节点变为好节点。显然这样操作并不会改变每个节点的儿子个数,于是我们成功建立了从一个不好的析合树到一个好的析合树的映射。
现在我们可以尝试 DP,设 \(f_{i,j} \in \{0,1\}\) 表示长度为 \(i\) 的排列能否构造出 \(j\) 个连续段。有两种转移方式:
- 新建一个有 \(k(k \geq 2)\) 个儿子的合点,其中第一个儿子为先前已有的节点,后面 \(k-1\) 个儿子都是叶子。这会产生 \(C_k - 1\) 个新的连续段,则转移为
- 新建一个有 \(k(k \geq 4)\) 个儿子的析点,其中第一个儿子为先前已有的节点,后面 \(k-1\) 个儿子都是叶子。这会产生 \(k\) 个新的连续段,则转移为
询问时查询 \(f_{n,k}\) 的值即可判断合法的方案是否存在。但此题还需要输出方案,我们考虑根据 \(f\) 数组和如何构造一个合法的排列。
考虑 DFS 处理,假设现在需要处理的区间为 \(l,r\),我们要在其中构造出 \(k\) 个连续段,设 \(L = r - l + 1\)。
-
如果 \(k = C_L\),直接构造一个有 \(r-l+1\) 个儿子的合点,它的所有儿子都是叶子。
-
需要在这个位置构造一个有 \(i\) 个儿子的合点。
必要条件是 \(k > C_i + L - i\) 以及 \(f_{L-i+1,k-C_i+1} = 1\),因为这个合点产生了 \(C_i-1\) 个连续段,需要递归的区间长度为 \(L-i+1\),这至少会再产生 \(L-i+1\) 个连续段。并且需要满足递归进去的区间能够构造出合法的排列。于是先递归第一个儿子,之后的儿子依次加即可。
但还有一个问题,合点的相邻儿子不能被合并,因此我们需要规定一些合点的顺序,额外记一维表示这个合点是正序/倒序即可。
- 需要在这个位置构造一个有 \(i\) 个儿子的析点。
同 \(2\),必要条件是 \(f_{L - i + 1,k-i}=1\)。对于其他儿子我们按照如下方式构造:按照奇偶分开排列,这样每个间隔的数在值域上一定不相邻。但有可能最后一个数与下一个区间形成连续段,与倒数第三个数交换一下即可。每组询问时间复杂度 \(O(n^2)\)。
Code
/*
也许所有的执念 就像四季的更迭
没有因缘 不需致歉
是否拥抱着告别 就更能读懂人间
还是感慨 更多一点
*/
#include <bits/stdc++.h>
#define pii pair<int, int>
#define mp(x, y) make_pair(x, y)
#define pb push_back
#define fi first
#define se second
#define int long long
#define mem(x, v) memset(x, v, sizeof(x))
#define mcpy(x, y, n) memcpy(x, y, sizeof(int) * (n))
#define lob lower_bound
#define upb upper_bound
using namespace std;
inline int read() {
int x = 0, w = 1;char ch = getchar();
while (ch > '9' || ch < '0') { if (ch == '-')w = -1;ch = getchar(); }
while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
return x * w;
}
inline int min(int x, int y) { return x < y ? x : y; }
inline int max(int x, int y) { return x > y ? x : y; }
const int MN = 1e2 + 5;
const int Mod = 1e9;
const int Inf = 2e18;
inline void Add(int &x, int y) { x += y; if (x >= Mod) x -= Mod; }
inline void Dec(int &x, int y) { x -= y; if (x < 0) x += Mod; }
inline int qPow(int a, int b = Mod - 2, int ret = 1) {
while (b) {
if (b & 1) ret = ret * a % Mod;
a = a * a % Mod, b >>= 1;
}
return ret;
}
// #define dbg
int N, K, Q, cnt, a[MN], c[MN];
int f[MN][MN * MN];
inline void DFS(int l, int r, int k, int o) {
int L = r - l + 1;
if (k == c[L]) {
for (int i = o ? r : l; o ? i >= l : i <= r; o ? i-- : i++) a[i] = ++cnt;
return;
}
for (int i = 2; i < L; i++) {
if (k <= c[i] + L - i || !f[L - i + 1][k - c[i] + 1]) continue;
DFS(o ? l + i - 1 : l, o ? r : r - i + 1, k - c[i] + 1, o ^ 1);
for (int j = o ? l + i - 2 : r - i + 2; o ? j >= l : j <= r; o ? j-- : j++) a[j] = ++cnt;
return;
}
for (int i = 4; i <= L; i++) {
if (!f[L - i + 1][k - i]) continue;
for (int j = r - i + 2; j <= r; j += 2) a[j] = ++cnt;
DFS(l, r - i + 1, k - i, 0);
for (int j = r - i + 3; j <= r; j += 2) a[j] = ++cnt;
if (i & 1) swap(a[r - 2], a[r]);
return;
}
}
signed main(void) {
for (int i = 1; i < MN; i++) c[i] = i * (i + 1) / 2;
f[0][0] = 1;
for (int i = 1; i < MN; i++) f[i][c[i]] = 1;
for (int i = 4; i < MN; i++) f[i][i + 1] = 1;
for (int i = 2; i < MN; i++)
for (int j = 1; j <= c[i]; j++) if (f[i][j]) {
for (int k = 2; k < MN - i; k++) f[i + k - 1][j + c[k] - 1] = 1;
for (int k = 4; k < MN - i; k++) f[i + k - 1][j + k] = 1;
}
Q = read();
while (Q--) {
N = read(), K = read();
if (!f[N][K]) puts("NO");
else {
puts("YES"), cnt = 0, DFS(1, N, K, 0);
for (int i = 1; i <= N; i++) printf("%lld%c", a[i], " \n"[i == N]);
}
}
return 0;
}