zoukankan      html  css  js  c++  java
  • facebook hacker cup2013资格赛第三题

    题意:m是长为n的数组,值均为非负数,前k个数按以下方式产生:

    (1)m[0]=a;

    (2)m[i] = (b * m[i - 1] + c) % r

    其中1≤k≤100000,1≤n, a, b, c, r≤1000000000

    对k ≤ j ≤ n - 1,m[j]是它前面k个数中没出现的最小非负数,形式地,m[j]=min{x|x ≥ 0 && x != m[j - 1] && x != m[j - 2] ... && x != m[j - k] }

    求m[n - 1]

    输入格式为:

    T(测试案例数,每个测试案例2行)

    n k

    a b c r

    n k

    a b c r

    .....

    解:

    首先要找规律,找个样例,用笨方法:把n个元素都按部就班算出来,打印输出看一下,比如:

    97 39
    34 37 656 97

    首先把n改大一点,便于发现规律,打印输出如下(每k个元素1行)

    34 71 82 4 28 43 16 84 78 50 81 64 17 24 89 69 8 79 87 92 83 41 39 62 40 2 51 21 75 36 48 7 42 76 73 59 26 66 91
    0 1 3 5 4 6 9 10 11 12 13 14 15 16 17 18 19 8 20 22 23 24 25 27 28 29 2 30 21 31 32 33 7 34 35 36 37 26 38
    39 0 1 3 5 4 6 9 10 11 12 13 14 15 16 17 18 19 8 20 22 23 24 25 27 28 29 2 30 21 31 32 33 7 34 35 36 37 26
    38 39 0 1 3 5 4 6 9 10 11 12 13 14 15 16 17 18 19 8 20 22 23 24 25 27 28 29 2 30 21 31 32 33 7 34 35 36 37
    26 38 39 0 1 3 5 4 6 9 10 11 12 13 14 15 16 17 18 19 8 20 22 23 24 25 27 28 29 2 30 21 31 32 33 7 34 35 36
    37 26 38 39 0 1 3 5 4 6 9 10 11 12 13 14 15 16 17 18 19 8 20 22 23 24 25 27 28 29 2 30 21 31 32 33 7 34 35
    36 37 26 38 39 0 1 3 5 4 6 9 10 11 12 13 14 15 16 17 18 19 8 20 22 23 24 25 27 28 29 2 30 21 31 32 33 7 34

    可以看到,除去第一行,以k+1为周期,这意味着,只需要算出紧跟在前k个元素后面的k+1个元素就可以了

    做题的时候,看到这一点就足够了,肯定没时间去证明,这里提供一个证明:

    证明:m[j] = m[j - (k + 1)]对2k + 1 ≤ j ≤ n - 1成立

    注意到:m[k - 1]之后的每个元素都在0~k这k+1个数之间,因为对任一个元素来讲,它不可以填前面k个数,所以0~k之间肯定有一个可以填

    所以m[k],m[k+1]...m[2k]都在0~k之间,而其中任意两个元素不能相等,否则就违反题目要求,所以m[k],m[k+1]...m[2k]是0,1,...,k的一个排列

    填m[2k+1]时,要求与m[k+1]...m[2k]不同,且最小,很显然,只能填m[k].更形式化一点:

    (1)填m[k]肯定满足“与m[k+1]...m[2k]不同”

    (2)填任一更小的非负数都会与m[k+1]...m[2k]中的某个相同

    填完后,m[k+1]...m[2k],m[2k+1]也是0,1,...,k的一个排列,可以继续往下推m[2k+2]的值……

    可以用数学归纳法完成这个证明。

    O(k^3)的解法:填m[k...2k]共k+1个元素,对每个元素:从x=0开始检查x是否在它前面k个元素中出现,如果出现,x++,重新检查;如果不出现,填上x

    注意到:填的每个元素都在0~k这k+1个数之间,因为对某个元素来讲,它不可以填前面k个数,所以0~k之间肯定有一个可以填

    这样,我们可以维护一个数组,记录哪些数可以填,哪些不可以填,具体地:cnt[0..k],cnt[i]表示当前元素之前k个元素中i出现的次数

    这样我们检查一个元素x是否在它前面k个元素中出现,就可以直接判断cnt[x]是否为0,这样复杂度就降到O(k^2),而1≤k≤100000,这就基本可用了

    复杂度如何进一步降低呢?

    考虑到我们每次都是从0开始检查,最坏情况要检查到k,有没有办法可以改进这一点呢?

    可以维护一个的最小优先队列(最小堆),只包含0~k中没在当前元素m[j]前面k个元素中出现的数,即priority_queue = {x|k ≥ x ≥ 0 && x != m[j - 1] && x != m[j - 2] ... && x != m[j - k] }

    这样每次取一个元素是O(lgk),整体复杂度为O(klgk)

    这个问题复杂度可以降到O(k),这个方法来自justever86同学

     为此,维护一个val值和与之相关的一个不变式:填当前元素时cnt[0],cnt[1],...cnt[val]均大于0

    这样我们就不必每次都是从0开始检查,可以从val+1开始检查,填完后,更新val的值

    具体地,

    (1)填m[k]时,从0开始检查,找到最小的x,使得cnt[x] == 0,m[k]赋值为x,val赋值为x(不变式显然成立).然后对idx从k+1到2k循环执行(2)

     (2)删去数组最前面的元素x

           1.如果x在0~k之间,更新cnt数组的计数,即cnt[x]--

       2.如果x <= val且cnt[x]==0,{m[idx]赋值为x,更新cnt[x],val保持不变}(解释:val前面连续的非零值中突然出现一个0,对应一个更小的可以填的数,将它填上,同时更新cnt,不变式仍成立);否则,从val + 1开始检查,找到最小的y,使得cnt[y] == 0,m[k]赋值为y,更新cnt[y],val赋值为y(不变式显然仍成立)

    这个不变式保证了每次填入的数都是符合要求的数,考虑到val只增不减,val终值至多为k(填完m[2k],val应该恰好是k,终值应该恰为k),所以内循环平摊时间为常数(平摊到每个外循环),因此,总的复杂度为O(k)

    //利用周期性,显然有周期性m[t] == m[t - (k + 1)]
    #include <stdio.h>
    #include <string.h>
    #include <vector>
    #include <iostream>
    #include <queue>
    #include <time.h>
    using namespace std;
    
    int T, n, k;
    long long a, b, c, r;
    int main(int argc, const char *argv[])
    {
        vector<int> m;
        vector<int> cnt;//cnt记录当前最新的k个元素的出现次数
        //freopen("in3.txt", "r", stdin);
        freopen("find_the_mintxt.txt", "r", stdin);
        //freopen("out3_1.txt", "w", stdout);
        cin >> T;
        for (int j = 1; j <= T; j++) {
            cin >> n >> k;
            cin >> a >> b >> c >> r;
            m.resize(2 * k + 1);
            cnt.resize(k + 1, 0);//resize真好用
            m[0] = a;
            if (a <= k) cnt[a]++;
            for (int i = 1; i < k; i++) {
                m[i] = (b * m[i - 1] + c) % r;
                if (m[i] <= k) cnt[m[i]]++;
            }
            for (int i = 0; i <= k; i++) {
                if(cnt[i] == 0){
                       m[k] = i;
                    cnt[i] = 1;
                    break;
                }
            }
            int val = m[k];
            //循环不变式为:cnt[0..val] >= 1
            for (int ptr = 0; ptr < k; ptr++) {
                //delete m[ptr]
                if (m[ptr] <= k) cnt[m[ptr]]--;
                if (m[ptr] <= val && cnt[m[ptr]] == 0) {
                    //下一个candidate是val + 1, 所以只要<=val都是更好的
                    m[ptr + k + 1] = m[ptr];
                    cnt[m[ptr]] = 1;//val不必更新
                }else{
                    while (cnt[++val]);
                    m[ptr + k + 1] = val;
                    cnt[val] = 1;
                }
            }
            
            
            int idx = n - 1 - (n - k - 1) / (k + 1) * (k + 1);
            //printf("idx:%d\n", idx);
            printf("Case #%d: %d\n", j, m[idx]);
            m.clear();
            cnt.clear();
        }
        //printf("time used: %.2lf seconds\n", (double)clock() / CLOCKS_PER_SEC);
        return 0;
    }
  • 相关阅读:
    LintCode "Maximum Gap"
    LintCode "Wood Cut"
    LintCode "Expression Evaluation"
    LintCode "Find Peak Element II"
    LintCode "Remove Node in Binary Search Tree"
    LintCode "Delete Digits"
    LintCode "Binary Representation"
    LeetCode "Game of Life"
    LintCode "Coins in a Line"
    LintCode "Word Break"
  • 原文地址:https://www.cnblogs.com/fstang/p/2883869.html
Copyright © 2011-2022 走看看