zoukankan      html  css  js  c++  java
  • 2018 ITMO Beijing Camp Day 2 ITMO U contest

    D. DotA Quals

    题意:有2^n (n <= 10)位选手,实力有一个绝对的排名,你处于第k (k <= 2^n) 位。每一轮剩下的人两两随机配对作战,一场对战中总是排名考前的人获胜,并且输家会退出比赛。让你求出你的期望轮数。

    观察:一共只会进行n轮比赛,P(X)表示X时间发生的概率,答案是P(打到第一轮)+ P(打到第二轮)+ ... + P(打到第n轮)。那么P(打到第i轮),计作P(i),该如何计算呢?画个图就比较明显了,为了参加第i轮,那么我们一定要在第i-1轮出线,脑补一下对战图(是一个二叉树),也就是让我们成为我们所在分赛站(在图中是一颗子树)所有2^(i-1) 队伍中最强的队伍,所以P(i) = cb((2^n)-k, (2^(i-1))-1)/cb((2^n)-1, (2^(i-1))-1)。这里cb(n, k) 指组合数,即从n个物品中选k个的方法数。公式中,分子代表从比我们弱的(2^n-k)个人中取(2^(i-1)-1)个人的方法数,-1是因为要除去我们自身,分母就是从所有除了自身的人中选出那些人的方法数。

    方法:因为要求精度,所以可以用log() 和 exp()。

    code:

     1 /*
     2  by skydog
     3  */
     4 #include <iostream>
     5 #include <cstdio>
     6 #include <vector>
     7 #include <utility>
     8 #include <algorithm>
     9 #include <cmath>
    10 #include <cstring>
    11 #include <map>
    12 #include <set>
    13 #include <stack>
    14 #include <queue>
    15 #include <deque>
    16 #include <cassert>
    17 #include <list>
    18 using namespace std;
    19 typedef long long ll;
    20 typedef pair<int, int> ii;
    21 typedef pair<ll, ll> l4;
    22 
    23 #define mp make_pair
    24 #define pb push_back
    25 
    26 const int N = 11;
    27 const int maxn = 1<<N;
    28 double f[maxn];
    29 double cb(int n, int k)
    30 {
    31     if (n < k || k < 0)
    32         return 0;
    33     return exp(f[n]-f[k]-f[n-k]);
    34 }
    35 int main()
    36 {
    37     f[1] = 0;
    38     for (int i = 2; i < maxn; ++i)
    39         f[i] = f[i-1] + log(i);
    40     int n, k;
    41     scanf("%d %d", &n, &k);
    42     double ans = 0;
    43     for (int i = 1; i <= n; ++i)
    44         ans += cb((1<<n)-k, (1<<(i-1))-1)/cb((1<<n)-1, (1<<(i-1))-1);
    45     printf("%.9lf
    ", ans);
    46 }
    View Code

    回顾:下次可以画个图,整体考虑一下。现场学弟想了一个dp[i][j],表示第i轮,有j个比自己强的人的概率。也可以推,但是比起正解麻烦了一点。

    G. Game of Chairs

    题意:c种颜色,有n把椅子排成一列,没把椅子有一个颜色a[i], (n, c <= 1e6,  1<= a[i] <= c)。保证每种颜色的椅子都有。下面让你选一把椅子坐下,裁判会随机说一个1-c的颜色,你要从你所在的椅子到一把对应颜色的椅子,经过的距离是椅子编号之差。下面请你选择最优的初始位置,使得期望移动距离最小,输出这个最小距离。

    观察

      每种颜色都是等概率取的,所以计算某一个位置i的期望只需要考虑到当前位置各个颜色的最短距离和D(i)就好了,不妨设D(i, k)为颜色为k的椅子与第i把椅子的最小距离,即D(i, k) = min{abs(i-j), where a[j] == k},那么D(i) = sum of { D(i, k) for 1 <= k <= c}。

      对于某一个位置i,暴力的计算D(i)是O(n)的。但是可以发现,对于每一种颜色k, D(i, k)关于i是一个分段函数,而且二次导数和一次导数的变化规律比较容易发现:当i为-inf的时候,D(i, k)的导数为-1, 即i每次向右移动一位(向最近的颜色为k的椅子移动),D(i, k)便减1。当经过第一把颜色为k的椅子时,D(i, k) = 0, 之后便开始上升(远离最近的颜色为k的椅子),导数为1,直到与下一把颜色为k的椅子的距离小于与上一把颜色为k的椅子的距离,D(i, k)又开始按照导数为-1的情况递减。

      画个图就比较容易看出来了,设ii, j为两个相邻颜色为k的椅子的位置,且ii < j, 即a[ii] == a[j] == k, ii < j , for all ii < l < j, a[l] != k。在ii之前D(i, k)导数为-1, 之后变成+1, 所以D(i, k)在ii的二次导数为+2, 当i位于ii和j中间的时候,导数又由+1(possibly 变为0)变为-1,中间可以令ceil((i+j)/2)的二次导数增加-1, 令floor((i+j)/2)的二次导数增加-1。

      在[-inf, 0]时,D(i, k)函数的导数是-1,即每次向正方向移动,距离都会减少1。然后对二次导求一遍前缀和,就可以得到一次导数。这里对于所有的颜色可以统一处理,一次导数的初始值就是-c。然后再计算一下初始位置为0时的答案,对一次导数再求一遍前缀和,就会得到每个位置的D(i)。取最小值即可。

    code:

     1 /*
     2  by skydog
     3  */
     4 #include <iostream>
     5 #include <cstdio>
     6 #include <vector>
     7 #include <utility>
     8 #include <algorithm>
     9 #include <cmath>
    10 #include <cstring>
    11 #include <map>
    12 #include <set>
    13 #include <stack>
    14 #include <queue>
    15 #include <deque>
    16 #include <cassert>
    17 #include <list>
    18 using namespace std;
    19 typedef long long ll;
    20 typedef pair<int, int> ii;
    21 typedef pair<ll, ll> l4;
    22 
    23 #define mp make_pair
    24 #define pb push_back
    25 ll gcd(ll a, ll b)
    26 {
    27     return b?gcd(b, a%b):a;
    28 }
    29 const int maxn = 1e6+10;
    30 int n, c, a[maxn], nxt[maxn];
    31 ll d[maxn];
    32 /*
    33  2-delta 1-delta 0-ans
    34  0 -c 0ans
    35  if encounter a color 2-d += 2
    36  if has next, then -1 -1
    37  */
    38 int main()
    39 {
    40     scanf("%d %d", &n, &c);
    41     for (int i = 1; i <= n; ++i)
    42         scanf("%d", a+i);
    43     for (int i = n; i >= 1; --i)
    44     {
    45         d[i] += 2;
    46         int &pos = nxt[a[i]];
    47         if (pos)
    48         {
    49             d[(i+pos)/2] -= 1;
    50             d[(i+pos+1)/2] -= 1;
    51         }
    52         pos = i;
    53     }
    54     for (int i = 1; i <= n; ++i)
    55         d[i] += d[i-1];
    56     d[0] = -c;
    57     for (int i = 1; i <= n; ++i)
    58         d[i] += -c;
    59     ll start = 0;
    60     for (int i = 1; i <= n; ++i)
    61     {
    62         int &pos = nxt[a[i]];
    63         if (pos)
    64         {
    65             start += i;
    66             pos = 0;
    67         }
    68     }
    69     ll ans = 1ll*n*n;
    70     for (int i = 1; i <= n; ++i)
    71     {
    72         start += d[i-1];
    73         if (start < ans)
    74             ans = start;
    75     }
    76     ll g = gcd(ans, c);
    77     ans /= g;
    78     c /= g;
    79     printf("%lld/%d
    ", ans, c);
    80 }
    View Code

    J. Journey

    题意:一个数轴上1到n(n<=1e5),每个位置上有一个步长a[i],起初你在1的位置,手里也有一个步长h0,1 <= a[i], h0 <= n-1。每次在一个位置i,你可以选择把自己步长变为a[i],或者不换,继续保持手中的步长。然后你可以向右(即数轴的正方向)移动手中步长单位的距离。问你最后到达位置n有多少种方式,答案mod 998 244 353。

    观察

      其实有一个比较明显的dp,或者说模拟,记dp(i, h)表示到达位置i,且手中步长为h的方法数有多少种。每次dp(i, h) 可以更新 dp(i+h, h),或者更新dp(i+a[i], a[i])。

      然后可以发现,dp(i, h) 可以更新dp(i+h, h), dp(i+2*h, h) ... 直到a[i+k*h] 等于h,那么此时dp(i+k*h + h, h) 会变成到达i+k*h位置的方法数之和。即dp(i+k*h, h) == dp(i, h) if a[i+h], ..., a[i+(k-1)*h] 都不等于h,或者说 到这些位置,且手中步长为h的方法数 都相同。

      那么我们便可以从左到右遍历,每次用dp(i, a[i])更新后续的状态,直到遇到某个a[j] == a[i],停止。

      分析时间复杂度,可以想到最坏的情况应该是一开始手中是1,a[1] = a[2] = 2, a[3] = a[4] = a[5] = 3, a[6-9] = 4,..., 总的操作次数是 n + n + n-2 + n - (2+3) + n-(2+3+4) = 2*n + sum of (n - (1+k)*k/2+1)。注意,k最多到O(sqrt(n)),所以整个算法是O(n*sqrt(n))的。其实这里是不是n * sqrt(n) 还需推敲。

    code:

     1 /*
     2  by skydog
     3  */
     4 #include <iostream>
     5 #include <cstdio>
     6 #include <vector>
     7 #include <utility>
     8 #include <algorithm>
     9 #include <cmath>
    10 #include <cstring>
    11 #include <map>
    12 #include <set>
    13 #include <stack>
    14 #include <queue>
    15 #include <deque>
    16 #include <cassert>
    17 #include <list>
    18 using namespace std;
    19 typedef long long ll;
    20 typedef pair<int, int> ii;
    21 typedef pair<ll, ll> l4;
    22 
    23 #define mp make_pair
    24 #define pb push_back
    25 
    26 const int maxn = 1e5+1;
    27 const int mod = 998244353;
    28 void add(int &a, int b)
    29 {
    30     a += b;
    31     if (a >= mod) a -= mod;
    32 }
    33 int a[maxn], h, n, d[maxn];
    34 void push(int pos, int h)
    35 {
    36     for (int i = pos+h; i <= n; i += h)
    37     {
    38         add(d[i], d[pos]);
    39         if (a[i] == h)
    40             break;
    41     }
    42 }
    43 int main()
    44 {
    45     scanf("%d %d", &n, &h);
    46     for (int i = 1; i <= n; ++i)
    47         scanf("%d", a+i);
    48     d[1] = 1;
    49     if (a[1] != h)
    50         push(1, h);
    51     for (int i = 1; i < n; ++i)
    52         push(i, a[i]);
    53     printf("%d
    ", d[n]);
    54 }
    View Code

    回顾:听室友说他们是用vector把dp状态存下来,然后过了题。为了避免mle,是用了析构的方法(destructor)。貌似就是建立一个局部vector变量,然后将全局的vector和局部的vector swap一下。

  • 相关阅读:
    ZJOI2017
    李超线段树
    单调性优化dp
    ZJOI2018 树
    【ZJOI2017】汉诺塔
    暂存
    聚类的方法(层次聚类,K-means聚类)
    哈希表(散列表)
    多路查找树B树
    二叉排序树
  • 原文地址:https://www.cnblogs.com/skyette/p/8433969.html
Copyright © 2011-2022 走看看