「csp校内训练 2019-10-24」解题报告
T1、猴猴吃苹果
(Description)
猴猴最喜欢在树上玩耍,一天猴猴又跳上了一棵树,这棵树有 (N (N leq 50000)) 个苹果,每个苹果有一个编号,分别为 (0) ~ (N - 1)
它们之间由 (N-1) 个树枝相连,猴猴可以从树枝的一端爬到树枝的另一端,所以猴猴可以从任意一个苹果的位置出发爬到任意猴猴想去的苹果的位置。
猴猴开始在编号为 (K (K < N)) 的苹果的位置,并且把这个苹果吃了,之后每一天猴猴都要去吃一个苹果,但是树上那么多苹果吃哪个呢?
猴猴想到自己去吃苹果时一定会把路上遇到的苹果都吃掉,于是猴猴决定去吃能让自己这天吃的苹果数量最多的那个苹果
如果有多个苹果满足条件,猴猴就会去吃这些中编号最小的苹果,那么猴猴会按照什么顺序吃苹果呢?
(Solution):
以 (K) 为根,每一次取一个叶子到根的路径;
可以反着考虑每个点是被哪个叶子删掉的 (一定是子树里最远的那个);
(f_i) 表示 (i) ( (i) 是叶子) 能够删掉的最远的节点,按 (f_i) 和编号排序即可。
(Source):
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <set>
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 = 5e4 + 5;
std::vector<int> ver[N];
int tmp[N];
int len[N], lef[N];
int n, rt;
int f[N], id[N];
inline bool cmp(const int &i, const int &j) {
if (f[i] == f[j])
return i < j;
return f[i] > f[j];
}
inline void jb(const int u, const int v) {
ver[u].push_back(v), ver[v].push_back(u);
}
void dfs(const int u, const int fa = -1) {
len[u] = 1, lef[u] = u;
for (unsigned i = 0; i < ver[u].size(); ++i) {
int v = ver[u][i];
if (v == fa)
continue;
dfs(v, u);
if (len[u] < len[v] + 1 || (len[u] == len[v] + 1 && lef[u] > lef[v]))
len[u] = len[v] + 1, lef[u] = lef[v];
}
chk_max(f[lef[u]], len[u]);
}
int main() {
//freopen("in", "r", stdin);
freopen("apple.in", "r", stdin);
freopen("apple.out", "w", stdout);
n = in(), rt = in() + 1;
if (n == 1)
return puts("0"), 0;
for (int i = 2; i <= n; ++i)
jb (i, in() + 1);
dfs(rt);
for (int i = 1; i <= n; ++i)
id[i] = i;
std::sort(id + 1, id + 1 + n, cmp);
printf("%d
", rt - 1);
for (int i = 1; i <= n; ++i)
if (f[id[i]])
printf("%d
", id[i] - 1);
return 0;
}
T2、猴猴吃香蕉
(Description):
猴猴最爱吃香蕉了。每天猴猴出门都会摘很多很多的香蕉,每个香蕉都有一个甜度;
猴猴不一定要把所有的香蕉都吃掉,猴猴每天都有一个心情值 (K);
猴猴希望当天吃的香蕉满足这么一个条件,这些香蕉的甜度乘积恰好等于 (K),但是猴猴并不知道有多少种方法,于是猴猴把这个问题交给你。
(Solution):
找出 (K) 的质因子,背包即可;
(不要写下面的铁憨憨写法)
(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, mod = 1e9 + 7;
int pri[10005], pcnt;
int n, m, nn;
int a[N], s[N], p[101], pre[101], c[101];
void init() {
for (int i = 2, j; i <= 10000; ++i) {
for (j = 2; j * j <= i; ++j)
if (i % j == 0)
break;
if (j * j > i)
pri[++pcnt] = i;
}
}
void prep() {
memset(c, 0, sizeof(c));
memset(s, 0, sizeof(s));
nn = 0;
for (int i = 1; i <= pcnt && pri[i] * pri[i] <= m; ++i) {
if (m % pri[i] == 0) {
p[++nn] = pri[i];
while (m % pri[i] == 0)
m /= pri[i], ++c[nn];
}
}
if (m != 1)
p[++nn] = m, c[nn] = 1;
pre[0] = 1;
for (int i = 1; i <= nn; ++i)
pre[i] = pre[i - 1] * (c[i] + 1);
for (int i = 1, tmp; i <= n; ++i)
for (int j = 1; j <= nn; ++j) {
tmp = 0;
while (a[i] % p[j] == 0)
a[i] /= p[j], ++tmp;
s[i] += pre[j - 1] * tmp;
}
//for (int i = 1; i <= n; ++i)
// printf("%d
", s[i]);
}
int dp() {
static int f[N];
memset(f, 0, sizeof(f));
f[0] = 1;
for (int i = 1; i <= n; ++i) {
for (int j = pre[nn] - s[i] - 1, k; j >= 0; --j) {
for (k = 1; k <= nn; ++k)
if (s[i] % pre[k] / pre[k - 1] + j % pre[k] / pre[k - 1] > c[k])
break;
if (k <= nn)
continue;
f[j + s[i]] += f[j];
if (f[j + s[i]] >= mod)
f[j + s[i]] -= mod;
}
}
return f[pre[nn] - 1];
}
int main() {
//freopen("in", "r", stdin);
freopen("banana.in", "r", stdin);
freopen("banana.out", "w", stdout);
init();
int T = in();
while (T--) {
n = in(), m = in();
for (int i = 1; i <= n; ++i) {
a[i] = in();
if (m % a[i])
--i, --n;
}
prep();
printf("%d
", dp());
}
return 0;
}
T3、猴猴的比赛
(Description):
猴猴今天要和小伙伴猩猩比赛爬树,为了公平不碰撞,猴猴和猩猩需要在不同的树上攀爬。
于是它们选了两颗节点数同为 (n) 的树,并将两棵树的节点分别以 (1)~(n) 标号(根节点标号为(1)),但两棵树的节点连接方式不尽相同。
现在它们决定选择两个标号的点进行比赛。为了方便统计,规定它们比赛中必须都向上爬(即选定的赛段节点(u)→节点(v) 都必须指向叶子方向)。
请你求出这两棵树上共有多少对节点满足比赛的需求。
(Solution):
记录第一棵树里的 (dfn);
(dfs) 遍历第二棵树,每个点对同时在两棵子树内的点有贡献;
遍历到点 (u) 时,数据结构维护第一棵树的贡献,回溯是删除;
遍历到点 (u) 时,计算点 (u) 作为子树中的点的贡献。
(Source):
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
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 = 1e5 + 5;
struct edge {
int next, to;
} e[N << 2];
int ecnt = 1, head[N << 1];
int n;
long long res;
struct binary_index_tree {
int t[N];
void modify(int p, int k) { for (; p <= n; p += (p & -p)) t[p] += k; }
int ask(int p, int ret = 0) { for (; p; p -= (p & -p)) ret += t[p]; return ret; }
} bit;
int dfn[N], siz[N];
void dfs1(const int u, const int fa = -1) {
siz[u] = 1;
dfn[u] = ++dfn[0];
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].to;
if (v == fa)
continue;
dfs1(v, u);
siz[u] += siz[v];
}
}
void dfs2(const int u, const int fa = -1) {
res += bit.ask(dfn[u - n]);
bit.modify(dfn[u - n], 1), bit.modify(dfn[u - n] + siz[u - n], -1);
for (int i = head[u]; i; i = e[i].next)
if (e[i].to != fa)
dfs2(e[i].to, u);
bit.modify(dfn[u - n], -1), bit.modify(dfn[u - n] + siz[u - n], 1);
}
int main() {
freopen("in", "r", stdin);
//freopen("climb.in", "r", stdin);
//freopen("climb.out", "w", stdout);
n = in();
for (int i = 1, x, y; i < n; ++i) {
x = in(), y = in();
e[++ecnt] = (edge){head[x], y}, head[x] = ecnt;
}
for (int i = 1, x, y; i < n; ++i) {
x = in() + n, y = in() + n;
e[++ecnt] = (edge){head[x], y}, head[x] = ecnt;
}
dfs1(1), dfs2(n + 1);
printf("%lld
", res);
return 0;
}