「csp校内训练 2019-11-06」解题报告
T1、华中
\(Description\):
严先生是华中科技大学的大一新生。
体育选课了,严先生作为幸运 Ex 的联创人才,选上了「户外运动」这门课。
严先生第一次课的内容是“定向越野”。
具体来说是这样的:校园内共有 \(n\) 个检查点,他们之间有 \(n - 1\) 条路径相连。
换句话说,检查点之间构成了一棵树。
为了让事情更有趣一点,严先生将这些检查点划分为两种,权值分别为 \(0\) 和 \(1\);
但是学校有打卡规定,在一次越野之后,经过的所有检查点的权值异或和必须为 \(0\)。
严先生的越野方案是这样的:从某一个检查点开始,不经过重复的检查点,随机的选择一个与当前检查点有路径相连的检查点,直到走到无路可走,这算完成一次定向越野。
但是严先生是联创团队 lab
组的优秀人才,严先生可以随意钦点每一个检查点的权值。
也就是说,严先生会让每一个点轮流做一次根节点,你需要让根节点到每一个叶子节点的路径权值异或和都为 \(0\) ,这就是一个合法的方案。
现在严先生想知道,总共有多少种合法的方案。输出结果对 $ 10^9 + 7 $ 取模。
$ 1 \leq n \leq 10^6 $。
\(Solution\):
先考虑 \(dp\),\(f_u\) 表示 \(u\) 为根的子树中的叶子节点到 \(u\) 的异或和相等的方案数;
显然有 \(f_u = 2 \prod f_v\),对于叶子有 \(f_u = 1\)。
直接 \(dp\) 换根即可。
由上面不难看出,每个非叶结点会对答案有 \(\times 2\) 的贡献;
设有 \(k\) 个叶子,考虑根是否为叶子,\(ans = (n - k) 2 ^ {n - k} + k \ 2 ^ {n - k + 1}\)。
\(Source\):
太占位置,不贴了。
T2、科技
\(Description\):
杨先生是另一名华中科技大学的大一新生。
与严先生一样,杨先生也是一名联创团队的优秀人才。作为 Web 组的新人,杨先生正在研究服务器的问题。
杨先生发现,团队内部总共有 \(n\) 台服务器,通过 \(n - 1\) 条数据链路进行连接。换句话说,服务器链接成了一个树的形状。每台服务器分配的任务不一样,因此第 \(i\) 台服务器有一个权值 \(v_i\),用于描述服务器的重要程度。
现在杨先生正在进行熬夜测试,他的任务是给每一个服务器确定一个负载值 \(w_i\) (\(w_i \leq 1\)) 和一个颜色 (黑或者白),使得对于每一个节点 \(u\),它的子树内的和它同色的节点负载值之和(包括它本身)与它的权值相等。
杨先生想知道熬夜测试的出题人有没有在刁难他,因此你需要告诉杨先生是否存在这么一个方案。如果存在输出"ICHOOSEUNIQUE",不存在输出"ICHOOSEBINGYAN"(均不含引号)。
当然啦,为了防止你骗分,杨先生对本题采用捆绑测试。
\(n \leq 1000 , v_i \leq 5000\)。
\(Solution\):
考虑一棵 \(u\) 为根的子树,当然希望子树里和 \(u\) 异色的负载和最小;
\(f_{u}\) 表示 \(u\) 的子树中,与 \(u\) 异色点负载和的最小值。
\(dp\) 即可。
\(g_{i, j}\) 表示 \(u\) 的前 \(i\) 个子树与 \(u\) 同色点的负载和为 \(j\) 的最小异色负载和。
转移讨论某个儿子与 \(u\) 是否异色即可:
\[g_{i,j} = \min\begin{cases}\min \{g_{i, j - val_v} + f_v\}\\\\\min \{g_{i, j - f_v} + val_v\}\\\end{cases} \\\\f_{u} = \min_{j = 0}^{val_u} \{g_{k, j}\}\]
\(Source\):
#include <cstdio>
#include <cstring>
#include <algorithm>
int in() {
int x = 0; char c = getchar(); bool f = 0;
while (c < '0' || c > '9')
f |= c == '-', c = getchar();
while (c >= '0' && c <= '9')
x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
return f ? -x : x;
}
template<typename T>inline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
template<typename T>inline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }
const int N = 1e3 + 5, M = 5e3 + 5, inf = 0x3f3f3f3f;
struct edge {
int next, to;
} e[N << 1];
int ecnt = 1, head[N];
int n, val[N];
int f[M];
void add_edge(const int u, const int v) {
e[++ecnt] = (edge){head[u], v}, head[u] = ecnt;
}
int t[M], s[M];
void dfs(const int u) {
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].to;
dfs(v);
}
memset(s, inf, sizeof(s));
s[0] = 0;
for (int i = head[u]; i; i = e[i].next) {
memset(t, inf, sizeof(t));
int v = e[i].to;
for (int j = val[u]; j >= 0; --j) {
if (j >= val[v])
chk_min(t[j], s[j - val[v]] + f[v]);
if (j >= f[v])
chk_min(t[j], s[j - f[v]] + val[v]);
}
std::swap(s, t);
}
for (int j = val[u]; j >= 0; --j)
chk_min(f[u], s[j]);
}
int main() {
//freopen("in", "r", stdin);
freopen("sciandtech.in", "r", stdin);
freopen("sciandtech.out", "w", stdout);
n = in();
for (int i = 2, fa; i <= n; ++i) {
fa = in();
add_edge(fa, i);
}
for (int i = 1; i <= n; ++i)
val[i] = in();
memset(f, inf, sizeof(f));
dfs(1);
puts((f[1] < inf) ? "ICHOOSEUNIQUE" : "ICHOOSEBINGYAN");
return 0;
}
T3、大学
\(Description\):
鸡先生也是一名华中科技大学的大一新生。
和大家预想的不太一样的是,鸡先生并没有加入联创团队,也不喜欢唱跳和球。
鸡先生正在准备冰岩作坊游戏组的实习任务,他现在遇到了一个棘手的问题。
鸡先生要构造出一个地图种子,具体来说,这是一个长度为 \(n\) 的 \(01\) 串 。然而鸡先生想要构造出更多的地图种子,因此鸡先生给出了 \(m\) 个区间 \([L_i,R_i]\) ,鸡先生可以对区间内的数进行重排。
每个区间只能操作一次,并且区间操作需要按照顺序依次进行。
对 \(m\) 个区间内的数全部重排之后,鸡先生想知道,这样可以得到多少个不同的 \(01\) 串?
为了方便你处理,鸡先生向你保证,这些区间的 \(L_i\) 是单调不降的,即保证了 \(L_i\leq L_{i+1}\) 。
由于答案有点大,因此你需要对 \(10^9+7\) (一个质数)取模。
\(1\leq n,m \leq 2000\)。
\(Solution\):
\(0,1\) 是对偶的,不妨只关注 \(1\) 。
重新理解一下重排:
题目要求重排,不妨不去思考如何将区间内的 \(0,1\) 进行分配,考虑每个位置放什么数。
不难想到一个 \(dp\):
\(f_{i,j}\) 表示前 \(i\) 个位置,放了 \(j\) 个 \(1\) 的方案数,但此时能用的 \(1\) 是 \(r_i\) 之前的所有 \(1\)。
(其中 \(r_i\) 表示覆盖到 \(i\) 的最靠右的右端点)。
为了方便处理边界,假设每个未被覆盖的位置都是长度为 \(1\) 的重排区间。
\(Source\):
#include <cstdio>
#include <cstring>
#include <algorithm>
int in() {
int x = 0; char c = getchar(); bool f = 0;
while (c < '0' || c > '9')
f |= c == '-', c = getchar();
while (c >= '0' && c <= '9')
x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
return f ? -x : x;
}
template<typename T>inline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
template<typename T>inline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }
const int N = 2005, mod = 1e9 + 7;
inline void add(int &_, int __) {
_ += __;
if (_ >= mod)
_ -= mod;
}
int n, m, r[N];
int f[N][N], pre[N];
char s[N];
int main() {
//freopen("in", "r", stdin);
freopen("university.in", "r", stdin);
freopen("university.out", "w", stdout);
n = in(), m = in();
scanf(" %s", s + 1);
for (int i = 1; i <= n; ++i)
r[i] = i, pre[i] = pre[i - 1] + (s[i] == '1');
for (int i = 1; i <= m; ++i) {
int x = in(), y = in();
for (int j = x; j <= y; ++j)
chk_max(r[j], y);
}
f[0][0] = 1;
for (int i = 1, cur = 1; i <= n; ++i, cur ^= 1) {
memset(f[cur], 0, sizeof(f[cur]));
for (int j = 0; j <= i && j <= pre[r[i]]; ++j) {
if (pre[r[i]] - j > r[i] - i)
continue;
f[cur][j] = f[cur ^ 1][j];
if (j) {
add(f[cur][j], f[cur ^ 1][j - 1]);
}
}
}
printf("%d\n", f[n & 1][pre[n]]);
return 0;
}