• 退役后的分治练习


    朋友和我说了一道美团面试题。让我对自己的智商产生了一些信心。

    这是一篇不成熟的文章,请告诉我怎么减少摘要的最少字数。

    题意

    告诉你(n,l,r,k),计算有多少个长度为(n)的数列,满足(l leq a\_i leq r,i in [1,n]),且(k | sum_i a_i)。结果对(1e9+7)取模。

    数据范围

    [n leq 1e5,k leq 100, l leq r leq 1e9 ]

    基本思路

    一个很好想到的做法是滚动的二维dp,(dp[i][j])表示前(i)个数字对(k)模数为(j)的方案数。那么转移方程为

    [dp[i][j] = sum dp[i-1][l] imes dp[i-1][(j-l) \% k] ]

    [dp[i] = dp[i-1] otimes dp[i-1] ]

    其中(otimes)表示循环卷积。

    优化1

    上面算法复杂度为(O(nk^2)),这个数据范围有点卡呢~

    注意到上面有个卷积!那不妨这个卷积优化一下。
    但是(k)也不大,就算优化成(O(nklog k))好像还是有点费劲。

    而且还要带精度的FFT,如果模数是(998244353)我会毫不犹豫NTT。

    优化2

    只能换做法了?

    这个题目其实每个位置不具有什么特殊性质,所以我们可以考虑把(n)拆成两段,后面一端是前面的延伸。这样看似乎有了新的方向!

    我们分治整个序列。那么这个式子大概可以表示成这个样子。

    [f(n)=f(mid) otimes f(n - mid) ]

    这里卷积不做优化了,直接最暴力的上,于是我们得到了下面这个(O(k^2 log_2 n))的做法。

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 105;
    typedef long long ll;
    const ll mod = 1e9 + 7;
    
    vector<ll> ans;
    
    inline ll calcIn(ll r, int k, int i) {
        if (r < i)
            return 0;
        return (r - i) / k + 1;
    }
    
    vector<ll> tmp, one;
    
    vector<ll> bake;
    inline void conv(vector<ll>& a, vector<ll>& b, int k) {
        assert((int)a.size() == k);
        assert((int)b.size() == k);
        bake.clear();
        for (int i = 0; i < k; i++)
            bake.push_back(a[i]), a[i] = 0;
    
        for (int i = 0; i < k; i++)
            for (int j = 0; j < k; j++)
                a[(i + j) % k] = (a[(i + j) % k] + bake[i] * b[j] % mod) % mod;
    }
    
    void solve(int n, int k, ll l, ll r) {
        if (n == 1) {
            for (int i = 0; i < k; i++)
                one[i] = ans[i] = calcIn(r, k, i) - calcIn(l - 1, k, i);
            return;
        }
        int mid = n >> 1;
        solve(mid, k, l, r);
        tmp.clear();
        for (vector<ll>::iterator it = ans.begin(); it != ans.end(); it++)
            tmp.push_back(*it);
        assert(mid == n - mid || mid == n - mid - 1);
        if (mid == n - mid - 1)
            conv(tmp, one, k);
        conv(ans, tmp, k);
    }
    
    int main() {
        int n, k;
        ll l, r;
        scanf("%d%lld%lld%d", &n, &l, &r, &k);
        ans.clear();
        one.clear();
        ans.resize(k);
        one.resize(k);
        solve(n, k, l, r);
        printf("%lld
    ", ans[0]);
        return 0;
    }
    

    欢迎留言指出问题!

    一个人没有梦想,和咸鱼有什么区别!
  • 相关阅读:
    php中文乱码处理方法
    Zend 官方框架增加 Swoole 协程支持 !
    矩阵行列式的向量表示
    ArduinoYun教程之ArduinoYun硬件介绍
    MIT 操作系统实验 MIT JOS lab1
    java File_encoding属性
    Java入门 第一季第五章 编程练习解析
    android 三种定位方式
    6.30
    OpenJudge百炼习题解答(C++)--题3142:球弹跳高度的计算
  • 原文地址:https://www.cnblogs.com/TABball/p/12727011.html
Copyright © 2020-2023  润新知