解题报告 smoj 2019初二创新班(2019.4.14)
解题报告 smoj 2019初二创新班(2019.4.14)
时间:2019.4.16
T1:进化树
题目描述
题目大意:奶牛的进化历程可以用一棵二叉树表示。每个节点的两个分支对应着这个特征进化/不进化。现在分别给出叶子节点各自拥有的特征,问能否构造一棵进化树满足条件。
都是图片
分析
考虑下面这样一棵进化树:
对应的文氏图:
可以发现,文氏图中任意两个圈之间不会相交,即两个圈要么包含,要么无关。
这使我们想到:对于文氏图中的每个圈(或者说:题目中出现过的每种特征),都判断一下是否有“越界”的行为。
判断的方法很简单:遍历每个出现过的特征,寻找包含这个特征的奶牛们。如果这些奶牛们都在同一个圈里,或者奶牛们所在的圈全部被该特征包含,那么就将这些奶牛所在集合合并。否则说明有奶牛所在的圈与该特征有交集但又不包含(即相交),输出"no"
。
代码
#include <bits/stdc++.h>
using namespace std;
const int kMaxN = 25 + 10;
struct UnionSet {
int father[kMaxN];
UnionSet() {
for (int i = 0; i < kMaxN; i++) father[i] = i;
}
int Find(int x) {
if (father[x] == x) {
return x;
} else {
return father[x] = Find(father[x]);
}
}
void Union(int x, int y) {
int i = Find(x);
int j = Find(y);
if (i != j) father[i] = j;
}
};
UnionSet S;
int n, k;
set<string> cow[kMaxN];
set<string> global;
set<int> group;
int main() {
freopen("2856.in", "r", stdin);
freopen("2856.out", "w", stdout);
ios::sync_with_stdio(0);
cin>>n;
for (int i = 1; i <= n; i++) {
cin>>k;
string str;
for (int j = 1; j <= k; j++) {
cin>>str;
cow[i].insert(str);
global.insert(str);
}
}
for (set<string>::iterator iter1 = global.begin();
iter1 != global.end(); iter1++) {
const string& str = *iter1;
group.clear();
for (int i = 1; i <= n; i++){
if (cow[i].count(str))
group.insert(S.Find(i));
}
if (group.size() >= 2) {
for (int i = 1; i <= n; i++) {
// 若奶牛i所在集合与str有交集,但i又不被str包含
if (group.count(S.Find(i)) && !cow[i].count(str)) {
printf("no\n");
return 0;
}
}
}
int x = *(group.begin());
for (set<int>::iterator iter2 = group.begin();
iter2 != group.end(); iter2++)
S.Union(x, *iter2);
}
printf("yes\n");
return 0;
}
T2:胡萝卜
题目描述
兔子经常感到饥饿,所以当他们外出吃胡萝卜时,他们会尽快跳起来。
胡萝卜种植在一条数轴上。
最初,兔子站在整数位置init。设兔子当前位置在整数x,她可以在单次跳跃中跳到位置4*x+3或位置8*x+7。它最多可以跳跃100000次。
胡萝卜种植在x位置,当且仅当x可被1000000007整除时(即胡萝卜种植在0号位置,位置1000000007,位置2000000014,依此类推)。
输出兔子能吃到胡萝卜所需的最小跳跃次数。如果使用最多100,000次跳跃无法获得胡萝卜,则返回-1。
分析
可以将胡萝卜理解为在模1000000007意义下的0。
找规律。发现\(4x +3 = 2 (2x + 1) + 1\),\(8x + 7 = 2(2(2x + 1) + 1) + 1\),结果与跳跃顺序无关,而且三次\(4x + 1\)刚好等于两次\(8x + 7\)。那么任意三次\(4x + 1\)都可以替换成两次\(8x + 7\)。显然\(8x + 7\)要更优,而且\(4x + 3\)只会跳跃\(0, 1\)或\(2\)次。
枚举\(8x + 7\)跳跃的次数,再枚举\(4x + 3\)跳跃的次数,任何操作在模1000000007意义下进行。时间复杂度\(O(MaxJump * 3)\)
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL kMod = 1000000007;
const LL kMaxJump = 100000;
int T;
LL x, y, ans;
int main() {
freopen("2858.in", "r", stdin);
freopen("2858.out", "w", stdout);
scanf("%d", &T);
while (T--) {
scanf("%lld", &x);
x %= kMod;
ans = kMaxJump + 1;
for (LL i = 0; i <= kMaxJump; i++) {
y = x;
for (int j = 0; j <= 3; j++) {
if (y == 0) ans = min(ans, i + j);
y = (y * 4 % kMod + 3) % kMod;
}
x = (x * 8 % kMod + 7) % kMod;
}
if (ans == kMaxJump + 1) {
printf("-1\n");
} else {
printf("%lld\n", ans);
}
}
return 0;
}
T3:干草
题目描述
有N头奶牛,P袋干草。每一袋干草都随机的选择分给N头奶牛中的某一头奶牛。
所有选择都是相互独立的。
当P袋干草分配结束后,得到干草最多的奶牛是“冠军奶牛”。
但有可能有多头奶牛都是“冠军奶牛”(因为它们得到同样多的干草,而且都是最多的),求出现多头奶牛是“冠军奶牛”的概率。
分析
考虑计算补集。求只有一头奶牛是“冠军奶牛”的概率。
不妨枚举冠军奶牛得到干草的数量\(x\),那么其他奶牛得到干草的数量就必须小于\(x\)。
设\(F(n, m, x)\)表示给前\(n\)头奶牛分配\(m\)堆干草,使所有奶牛获得干草数量\(\le x\)的概率。
设\(P(n, m, c)\)表示给前\(n\)头奶牛分配\(m\)堆干草,使第\(n\)头奶牛获得的干草数量刚好为\(c\)的概率。
转移:
枚举\(x\),因为冠军奶牛可能是\(n\)头中的任意一头,所以\(ans\)加上\(F(n - 1, p - x, x - 1) \times P(n, p, x) \times n\)
最后输出\(1 - ans\)即可。时间复杂度:\(O(n ^ 3)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int kMaxN = 50 + 10;
double arr_p[kMaxN][kMaxN][kMaxN];
double P(int n, int m, int c) {
if (m == 0) {
return c == 0 ? 1.0 : 0.0;
} else if (c == 0) {
return pow(1.0 - 1.0 / n, m);
} else if (arr_p[n][m][c] != -1) {
return arr_p[n][m][c];
} else {
return arr_p[n][m][c] = (1.0 / n) * P(n, m - 1, c - 1)
+ (1.0 - 1.0 / n) * P(n, m - 1, c);
}
}
double arr_f[kMaxN][kMaxN][kMaxN];
double F(int n, int m, int x) {
if (n == 0) {
return 1.0;
} else if (n == 1) {
return m <= x ? 1.0 : 0.0;
} else if (arr_f[n][m][x] != -1) {
return arr_f[n][m][x];
} else {
double ans = 0;
for (int c = 0; c <= min(m, x); c++) {
ans += F(n - 1, m - c, x) * P(n, m, c);
}
return arr_f[n][m][x] = ans;
}
}
int T;
int n, p;
int main() {
freopen("2859.in", "r", stdin);
freopen("2859.out", "w", stdout);
scanf("%d", &T);
while (T--) {
scanf("%d %d", &n, &p);
swap(n, p); // 题面出锅
for (int i = 0; i < kMaxN; i++)
for (int j = 0; j < kMaxN; j++)
for (int k = 0; k < kMaxN; k++) {
arr_f[i][j][k] = -1;
arr_p[i][j][k] = -1;
}
double ans = 0;
for (int x = 1; x <= p; x++) {
ans += F(n - 1, p - x, x - 1) * P(n, p, x) * n;
}
printf("%lf\n", 1.0 - ans);
}
return 0;
}