2019.10.29
- A : 结论题,博弈
- B : 树形 Dp ,换根
- C : 结论题,并查集,Dfs
A
发现只有一个石头的时候先手必胜,反之后手必胜。可以递归证明(数学归纳法) 的样子 。
每组石头之间组合后的输赢是可以异或起来的。
B
挺水的树形 Dp ,虽然一开始状态没弄好导致很难打。
首先考虑数量。这是一个很简单的东西,看看 $ k $ 的范围就可以秒掉。
令 $ f[i][j] $ 表示 $ i $ 的子树内距离 $ i $ 小于等于 $ j $ 的点的数量,对于 $ u, v $ ,其中 $ v $ 是 $ u $ 的儿子:
因为距离 $ v $ 是 $ j - 1 $ 那么距离 $ u $ 就是 $ j $ 了。
之后再换根得到对于整棵树的情况(怎么上来怎么下去,具体看代码)。
再考虑乘积。令 $ g[i][j] $ 表示 $ i $ 的子树内距离 $ i $ 小于等于 $ j $ 的点 (在只有小于等于 $ j $ 的点走到 $ i $ 的情况下) 的贡献之积(手动断句)。
同上:
换根的时候要注意要先把子树内原来的贡献除掉,再传下去,有一点细节,看代码吧。
其实因为有取模,所以做除法的时候还要考虑没有逆元的情况。
但是因为这道题只有乘法,没有加法,而且初值不可能是模数的倍数,因此也不会出现中间出现模数的倍数的情况。
代码里的 $ ft , gt $ 分别对应树外不包括自己的情况。
注意最后合并 $ g $ 的时候,因为一开始统计的自身只包括了树内的点,而实际上自身的贡献是要包括树外的,所以要先把原来的 $ f $ 除掉,再乘上新的 $ f $ (其他点对于 $ u $ 而言都只用考虑树外或者树内一种,所以不用改变)。
int n, m;
struct EDGE {
int to, nex;
} edge[N << 1];
int head[N], cntedge;
void Addedge(int u, int v) {
edge[++cntedge] = (EDGE) { v, head[u] };
head[u] = cntedge;
}
int f[N][M], g[N][M];
int ft[N][M], gt[N][M];
int Pow(int x, int k) {
int ans = 1;
for ( ; k > 0; x = 1ll * x * x % Mod, k >>= 1)
((k & 1) && (ans = 1ll * ans * x % Mod));
return ans;
}
void Dfs(int u, int fa) {
rep (j, 0, m) f[u][j] = 1;
for (int i = head[u], v; i; i = edge[i].nex) {
v = edge[i].to;
if (v == fa) continue;
Dfs(v, u);
rep (j, 1, m) f[u][j] += f[v][j - 1];
}
rep (j, 0, m) g[u][j] = f[u][j];
for (int i = head[u], v; i; i = edge[i].nex) {
v = edge[i].to;
if (v == fa) continue;
rep (j, 1, m) g[u][j] = 1ll * g[u][j] * g[v][j - 1] % Mod;
}
}
void Back(int u, int fa) {
rep (j, 0, m) {
ft[u][j] = ft[u][j] + f[u][j];
gt[u][j] = 1ll * gt[u][j] * Pow(f[u][j], Mod - 2) % Mod;
gt[u][j] = 1ll * gt[u][j] * ft[u][j] % Mod * g[u][j] % Mod;
}
for (int i = head[u], v; i; i = edge[i].nex) {
v = edge[i].to;
if (v == fa) continue;
ft[v][1] = 1;
rep (j, 1, m - 1) ft[v][j + 1] = ft[u][j] - f[v][j - 1];
gt[v][0] = 1, gt[v][1] = 1;
rep (j, 1, m - 1) gt[v][j + 1] =
1ll * gt[u][j] * Pow(g[v][j - 1], Mod - 2) % Mod
* Pow(ft[u][j], Mod - 2) % Mod * (ft[u][j] - f[v][j - 1]) % Mod;
Back(v, u);
}
}
int main() {
n = read<int>(), m = read<int>();
rep (i, 2, n) {
int x = read<int>(), y = read<int>();
Addedge(x, y); Addedge(y, x);
}
rep (j, 0, m) gt[1][j] = 1;
Dfs(1, 0), Back(1, 0);
rep (i, 1, n) printf("%d%c", ft[i][m], " \n"[i == n]);
rep (i, 1, n) printf("%d%c", gt[i][m], " \n"[i == n]);
return 0;
}
C
如果我们给输入的 $ A, B $ (牌的正反两面) 之间连一条边。
那么对于每条边,他只能选择一个端点。
最后形成了一个图,对于图上的每个联通块,如果是一棵树,而且没有重边,那么就做不到所有的点都被选择,因为树只有 $ n - 1 $ 条边,却又 $ n $ 个点,但是如果有了重边,或者是一个带环的图,就可以做到。
那么我们对于所有的树,找出其中最小的点 $ mi $ 和最大的点 $ mx $ ,如果 $ l, r $ 完全包含 $ mi, mx $ 那么就不行,否则一定可以做到满足。
对所有的 $ r $ 维护小于等于其的右端点中最大的左端点,这样就可以直接查询了。
int n, m, q;
int A[N], B[N];
int Fa[N], mi[N], mx[N], cnt[N], edg[N];
int MxL[N];
int Find(int x) { return x == Fa[x] ? x : Fa[x] = Find(Fa[x]); }
void Uni(int x, int y) {
x = Find(x), y = Find(y);
if (x != y) {
Fa[x] = y, cnt[y] += cnt[x], edg[y] += edg[x] + 1;
mi[y] = min(mi[y], mi[x]);
mx[y] = max(mx[y], mx[x]);
} else ++edg[y];
}
int main() {
m = read<int>(), n = read<int>();
rep (i, 1, m) Fa[i] = i, mi[i] = mx[i] = i, cnt[i] = 1;
rep (i, 1, n) {
A[i] = read<int>(), B[i] = read<int>();
Uni(A[i], B[i]);
}
rep (i, 1, m) if (Find(i) == i) {
int x = Fa[i];
if (cnt[x] <= edg[x]) continue;
MxL[mx[x]] = mi[x];
}
rep (i, 1, m) MxL[i] = max(MxL[i], MxL[i - 1]);
q = read<int>();
rep (i, 1, q) {
int l = read<int>(), r = read<int>();
puts(MxL[r] < l ? "Yes" : "No");
}
return 0;
}