解题报告 smoj 2019初二创新班(2019.4.14)

解题报告 smoj 2019初二创新班(2019.4.14)

时间:2019.4.16

比赛网址

T1:进化树

题目描述

题目大意:奶牛的进化历程可以用一棵二叉树表示。每个节点的两个分支对应着这个特征进化/不进化。现在分别给出叶子节点各自拥有的特征,问能否构造一棵进化树满足条件。

都是图片

1538275-20190416170826431-1284553670.png

1538275-20190416170844618-213350437.png

分析

考虑下面这样一棵进化树:

1538275-20190416170851468-1409879488.png

对应的文氏图

1538275-20190416170859954-503870452.png

可以发现,文氏图中任意两个圈之间不会相交,即两个圈要么包含,要么无关。

这使我们想到:对于文氏图中的每个圈(或者说:题目中出现过的每种特征),都判断一下是否有“越界”的行为。

判断的方法很简单:遍历每个出现过的特征,寻找包含这个特征的奶牛们。如果这些奶牛们都在同一个圈里,或者奶牛们所在的圈全部被该特征包含,那么就将这些奶牛所在集合合并。否则说明有奶牛所在的圈与该特征有交集但又不包含(即相交),输出"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​\)的概率。

转移:
\[ \large \begin {aligned} & \text {对于F,枚举第n头奶牛获得干草的数量c:} \\ & F(n, m, x) = \displaystyle \sum _ {c \le \min (m, x)} F(n - 1, m - c, x) \times P(n, m, c) \\ & \text {对于P,最后一堆干草要么刚好分给n,要么分给其他奶牛:} \\ & P(n, m, c) = \dfrac {1} {n} \times P(n - 1, m - 1, c - 1) + \dfrac {n - 1} {n} \times P(n - 1, m - 1, c)\\ \end {aligned} \]

枚举\(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;
}

转载于:https://www.cnblogs.com/longlongzhu123/p/10718375.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值