C - Tree Restoring
看到“树上最远点距离”这种东西,直径应该就有一些性质
把直径抽出来,直径中点的数值是最小的,当然可能有 (2) 个,但如果 (>2) 个就没戏了
然后直径的两端一定是对称且从中间向两边每次 (+1),如果从最小值到最大值某个值出现的次数 (<2) 也没戏了
还有一个判断是最大值与最小值之间的关系,如果中点在点上,需满足 (max=2 imes min),否则需要判断 (max = 2 imes min - 1)
以上条件都满足一定是可以构造出来的
#include <bits/stdc++.h>
using namespace std;
void read (int &x) {
char ch = getchar(); x = 0; while (!isdigit(ch)) ch = getchar();
while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
} const int N = 105;
int n, mn, mx, a[N], c[N];
#define fail return puts ("Impossible"), 0
#define success return puts ("Possible"), 0
signed main() {
read (n); mn = n;
for (int i = 1; i <= n; ++i) read (a[i]), ++c[a[i]];
for (int i = 1; i <= n; ++i)
mn = min (a[i], mn), mx = max (a[i], mx);
if (c[mn] > 2) fail;
int mm = c[mn] == 1 ? mn * 2 : mn * 2 - 1;
if (mx != mm) fail;
for (int i = mn + 1; i <= mx; ++i)
if (c[i] < 2) fail;
success;
return 0;
}
D - ~K Perm Counting
以前写过了,复制过来
key:容斥,把相互关联的数串成链,在链上dp
dp算出有至少 (k) 个不合法的方案进行容斥
在原序列上很难dp,把一些关联的数串成链
一个数 (x) 不可以填在 (x+k),也不可以填在 (x-k),就把这样一些数串起来,一个位置一个数值一个位置一个数值....
中间的边选了一条就代表一个不合法,不能选相邻两条边
这样每条链没有重复的部分。在每条链开头打上标记就可以拼接起来处理了。然后就是简单dp
(f_{i,j,k}) 表示前 (i) 个,选了 (j) 条,最后一条有没有选,在链头需要特判
#include <bits/stdc++.h>
using namespace std;
#define int long long
void read (int &x) {
char ch = getchar(); x = 0; while (!isdigit(ch)) ch = getchar();
while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
} const int N = 2005, mod = 924844033;
int n, k, res, cnt, f[N << 1][N][2], pw[N], is[N << 1];
signed main() {
read (n), read (k); pw[0] = 1;
for (int i = 1; i <= n; ++i) pw[i] = pw[i - 1] * i % mod;
for (int i = 1; i <= k; ++i) {
for (int t = 0; t < 2; ++t)
for (int j = i; j <= n; j += k) is[++cnt] = (i != j);
} f[0][0][0] = 1;
for (int i = 1; i <= cnt; ++i)
for (int j = 0; j <= n; ++j) {
f[i][j][0] = (f[i - 1][j][0] + f[i - 1][j][1]) % mod;
if (is[i] && j) f[i][j][1] = f[i - 1][j - 1][0];
}
for (int i = 0, t = 1; i <= n; ++i, t = -t)
(res += t * (f[cnt][i][0] + f[cnt][i][1]) * pw[n - i]) %= mod;
return printf ("%lld
", (res + mod) % mod ), 0;
}
E - Sugigma: The Showdown
如果步数有限:从根节点往下拓展,枚举最后停留在每一个点的答案。如果最后能停在 (x) 点,从根到 (x) 的路径都要能走通(比追赶着先到),这个好办,如果被抓了直接 (return),不必再处理子树
无限的情况:无限步数一定是在“耍猴”了,就是在 (2) 个相邻点之间来回跳,但追赶者的树上这两个点的距离 (>2),永远也追不到。但前提是到达“耍猴点”之前没有被抓住
#include <bits/stdc++.h>
using namespace std;
void read (int &x) {
char ch = getchar(); x = 0; while (!isdigit(ch)) ch = getchar();
while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
} const int N = 2e5 + 5, M = N << 1;
int n, x, y, res, d[N], fa[N];
vector<int> gx[N], gy[N];
#define pb push_back
void dfs (int u, int la) {
d[u] = d[la] + 1, fa[u] = la;
for (int v : gy[u]) if (v != la) dfs (v, u);
}
int dist (int x, int y) {
int num = 0;
if (d[x] < d[y]) swap (x, y);
if (d[x] - d[y] > 2) return 1;
while (d[x] != d[y]) ++num, x = fa[x];
while (x != y) {
x = fa[x], y = fa[y];
if ((num += 2) > 2) return 1;
}
return 0;
}
void dfs (int u, int la, int dp) {
if (dp >= d[u]) return; res = max (res, d[u]);
for (int v : gx[u]) {
if (v == la) continue;
if (dist (u, v)) { puts ("-1"); exit (0); }
dfs (v, u, dp + 1);
}
}
signed main() {
read (n), read (x), read (y);
for (int i = 1, u, v; i < n; ++i)
read (u), read (v), gx[u].pb (v), gx[v].pb (u);
for (int i = 1, u, v; i < n; ++i)
read (u), read (v), gy[u].pb (v), gy[v].pb (u);
d[0] = -1; dfs (y, 0); dfs (x, 0, 0);
return printf ("%d
", res << 1), 0;
}
F - Many Easy Problems
先进行一点点的转换,每个连通块都是一棵子树,而树中 (num(点)=num(边)+1)。因为树上每条边断开都能把树分为两块,所以边往往具有更奇妙的性质
对于每一个点集,如果一条边被取,当且仅当这条边的左右两部分都有点在集合中
然后就好办了,对每一条边考虑,有 (f(s)=C_{n}^{s}+sumlimits_{i=1}^{m}C_{n}^{s}-C_{x_i}^{s}-C_{y_i}^{s}),啥意思呢?(x_i,y_i) 表示把边 (i) 断开后两部分的大小,就是用所有情况减去只在某一边有点的情况。第一个 (C_{n}^{s}) 就是 (num(点)=num(边)+1) 的 (1)
接下来展开
(f(s)=C_{n}^{s}+(n-1)C_{n}^{s}-sumlimits_{i=s}^{n}cnt_i imes C_{n}^{i}),(cnt_i) 表示 (x_i,y_i) 中数值为 (i) 的数量
就展开:
(f(s)=nC_{n}^{s}-sumlimits_{i=s}^{n}frac{cnt_i imes i!}{s! imes (i-s)!}=nC_{n}^{s}-frac{1}{s!}sumlimits_{i=s}^{n}frac{cnt_i imes i!}{(i-s)!})
好家伙,把 (cnt_i imes i!) 设为 (A_i),((i-s)!) 设为 (B_i),这不是个 (ntt) 的板子!
#include <bits/stdc++.h>
using namespace std;
#define int long long
void read (int &x) {
char ch = getchar(); x = 0; while (!isdigit(ch)) ch = getchar();
while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
} const int N = 2e5 + 5, M = N << 1, mod = 924844033;
int qpow (int x, int y) {
int t = 1;
while (y) {
if (y & 1) t = t * x % mod;
x = x * x % mod, y >>= 1;
} return t;
}
int n, cnt, h[N], nxt[M], to[M], cc[N], sz[N];
void add (int u, int v) {
to[++cnt] = v, nxt[cnt] = h[u], h[u] = cnt;
}
void dfs (int u, int la) {
sz[u] = 1;
for (int i = h[u], v; i; i = nxt[i])
if ((v = to[i]) != la) dfs (v, u), sz[u] += sz[v];
if (u != 1) ++cc[sz[u]], ++cc[n - sz[u]];
}
int pw[N], in[N];
int C (int x, int y) {
return pw[x] * in[y] % mod * in[x - y] % mod;
}
int lim = 1, len, rev[N << 2], a[N << 2], b[N << 2];
void ntt (int *a, int opt) {
for (int i = 0; i < lim; ++i)
if (i < rev[i]) swap (a[i], a[rev[i]]);
for (int m = 1; m < lim; m <<= 1) {
int tmp = qpow (5, (mod - 1) / (m * 2));
if (opt == -1) tmp = qpow (tmp, mod - 2);
for (int i = 0; i < lim; i += (m << 1)) {
int o = 1;
for (int j = 0; j < m; ++j, o = o * tmp % mod) {
int t = o * a[i + j + m] % mod, u = a[i + j];
a[i + j] = (u + t) % mod, a[i + j + m] = (u - t + mod) % mod;
}
}
}
if (opt == -1) for (int i = 0, in = qpow (lim, mod - 2); i < lim; ++i) a[i] = a[i] * in % mod;
}
signed main() {
read (n); pw[0] = 1;
for (int i = 1; i <= n; ++i) pw[i] = pw[i - 1] * i % mod;
in[n] = qpow (pw[n], mod - 2);
for (int i = n; i >= 1; --i) in[i - 1] = in[i] * i % mod;
for (int i = 1, u, v; i < n; ++i)
read (u), read (v), add (u, v), add (v, u);
dfs (1, 0);
while (lim <= n + n) lim <<= 1, ++len;
for (int i = 0; i < lim; ++i)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (len - 1));
for (int i = 0; i <= n; ++i) a[i] = cc[i] * pw[i] % mod;
for (int i = 0; i <= n; ++i) b[i] = in[i] % mod;
reverse (a, a + n + 1);
ntt (a, 1), ntt (b, 1);
for (int i = 0; i < lim; ++i) a[i] = a[i] * b[i] % mod;
ntt (a, -1);
for (int i = 1; i <= n; ++i) {
int res = n * C (n, i) - in[i] * a[n - i];
printf ("%lld
", (res % mod + mod) % mod);
}
return 0;
}