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;
    }
  • 相关阅读:
    CodeForces 825G"Tree Queries"(选根建树)
    技术日记
    [express.js 使用笔记] ajax询问数据,却显示在浏览器上,该怎么办?
    node.js 基础和文件操作 笔记
    JSON 笔记
    CSS 学习笔记(一)选择器
    cf1321E
    [学习笔记] 后缀数组
    Python 编程练习
    《明朝那些事儿》 读书笔记
  • 原文地址:https://www.cnblogs.com/fstang/p/2883869.html
Copyright © 2011-2022 走看看