Find All Possible Stable Binary Arrays II

Find All Possible Stable Binary Arrays II

You are given 3 positive integers zeroone, and limit.

binary array arr is called stable if:

  • The number of occurrences of 0 in arr is exactly zero.
  • The number of occurrences of 1 in arr is exactly one.
  • Each subarray of arr with a size greater than limit must contain both 0 and 1.

Return the total number of stable binary arrays.

Since the answer may be very large, return it modulo 109 + 7.

 

Example 1:

Input: zero = 1, one = 1, limit = 2

Output: 2

Explanation:

The two possible stable binary arrays are [1,0] and [0,1].

Example 2:

Input: zero = 1, one = 2, limit = 1

Output: 1

Explanation:

The only possible stable binary array is [1,0,1].

Example 3:

Input: zero = 3, one = 3, limit = 2

Output: 14

Explanation:

All the possible stable binary arrays are [0,0,1,0,1,1][0,0,1,1,0,1][0,1,0,0,1,1][0,1,0,1,0,1][0,1,0,1,1,0][0,1,1,0,0,1][0,1,1,0,1,0][1,0,0,1,0,1][1,0,0,1,1,0][1,0,1,0,0,1][1,0,1,0,1,0][1,0,1,1,0,0][1,1,0,0,1,0], and [1,1,0,1,0,0].

 

Constraints:

  • 1 <= zero, one, limit <= 1000

 

解题思路

  一开始状态定义的不好,导致优化起来巨复杂。为了方便这里重新定义符号 a=zerob=onek=limitn=a+b

  容易知道当 1 的位置确定后,0 的位置也就确定了,所以我们可以只关注 1 的位置。定义 f(i,j) 表示在前 j 个位置中放入了 i1 且第 j 个位置是 1 的方案数。根据最后一段的 1 的个数及其前一段 0 的个数进行状态划分。

  具体来说如果最后一段有 u1,显然 1uk,其前一段 0 的个数为 v,同样满足 1vk,则有 f(i,j)f(iu,juv)。所以状态转移方程就是:f(i,j)=u=1kv=1kf(iu,juv)

  显然直接暴力 dp 的话时间复杂度为 O(bnk2)。我们把上面的式子展开:

f(i1,j2)+f(i1,j3)+f(i1,j4)++f(i1,j1k)+f(i2,j3)+f(i2,j4)++f(i2,j1k)+f(i2,j2k)+f(i3,j4)++f(i3,j1+k)+f(i3,j2k)+f(i3,j3k)+f(ik,j1k)+f(ik,j2k)+f(ik,j3k)++f(ik,jkk)

  可以发现有点类似于平行四边形。再考虑 f(i,j+1)

f(i1,j1)+f(i1,j2)+f(i1,j3)++f(i1,jk)+f(i2,j2)+f(i2,j3)++f(i2,jk)+f(i2,j1k)+f(i3,j3)++f(i3,j+k)+f(i3,j1k)+f(i3,j2k)+f(ik,jk)+f(ik,j1k)+f(ik,j2k)++f(ik,j+1kk)

  与 f(i,j) 相比 f(i,j+1) 只多出了红色的部分,可以看作是以 f(i1,j1) 为端点的对角线上 kf 的和,即 u=0k1f(i1u,j1u)。另外减少的部分是 u=0k1f(i1u,j1ku)

  为此我们可以维护一个 g(i,j)=u=0k1f(iu,ju),转移方程为 g(i,j)=g(i1,j1)+f(i,j)f(ik,jk)

  这样当我们确定 i 再枚举 j 时,可以维护一个大小为 k 的滑动窗口,并用 s 来维护窗口内的 g 的和。当枚举到 ij,有 ss+g(i1,j2)g(i1,jk2),然后有 f(i,j)=s

  另外边界情况,即枚举到 i 时前 i 个位置均为 1,此时有 f(i,i)=1,当然只有 ik 时才成立。剩下的细节见代码。

  最后根据最后一段 0 的不同个数来统计答案,即 i=0min{k,n}f(b,ni)

  AC 代码如下,时间复杂度为 O(b×n)

class Solution {
public:
    int mod = 1e9 + 7;
    
    int numberOfStableArrays(int a, int b, int k) {
        int n = a + b;
        vector<vector<int>> f(b + 1, vector<int>(n + 1)), g(b + 1, vector<int>(n + 1));
        f[0][0] = g[0][0] = 1;
        for (int i = 1; i <= b; i++) {
            if (i <= k) f[i][i] = 1;
            for (int j = i + 1, s = 0; j <= n; j++) {
                s = (s + g[i - 1][j - 2]) % mod;
                if (j - k - 2 >= 0) s = (s - g[i - 1][j - k - 2]) % mod;
                f[i][j] = s;
            }
            // g[i][0] = f[i][0];
            for (int j = 1; j <= n; j++) {
                g[i][j] = (g[i - 1][j - 1] + f[i][j]) % mod;
                if (i - k >= 0 && j - k >= 0) g[i][j] = (g[i][j] - f[i - k][j - k]) % mod;
            }
        }
        int ret = 0;
        for (int i = 0; i <= k && i <= n; i++) {
            ret = (ret + f[b][n - i]) % mod;
        }
        ret = (ret + mod) % mod;
        return ret;
    }
};

   事实上如果把状态定义成 f(i,j,0/1) 表示放置了 i0j1 且最后一段是 0/1 的方案数,那么就会变得十分简单了。反正我是想不到的,关键点应该是要发现对于任意一个合法方案,每一段的 01 是交替出现的。

  状态转移方程就很简单了。

{f(i,j,0)=u=1kf(iu,j,1)f(i,j,1)=u=1kf(i,ju,0)

  优化的地方就很容易看到了,也是用一个大小为 k 的滑动窗口来维护 f。其中对于 f(i,j,0),应该在每一列开一个单调队列。而对于 f(i,j,1),则应该在每一行开一个单调队列。

  最后答案就是 f(a,b,0)+f(a,b,1)

  AC 代码如下,时间复杂度为 O(a×b)

class Solution {
public:
    int mod = 1e9 + 7;
    
    int numberOfStableArrays(int a, int b, int k) {
        vector<vector<vector<int>>> f(a + 1, vector<vector<int>>(b + 1, vector<int>(2)));
        vector<int> c(b + 1), r(a + 1);
        for (int i = 1; i <= k && i <= a; i++) {
            f[i][0][0] = 1;
        }
        for (int i = 1; i <= k && i <= b; i++) {
            f[0][i][1] = 1;
        }
        for (int i = 1; i <= a; i++) {
            for (int j = 1; j <= b; j++) {
                if (i - 1 >= 0) c[j] = (c[j] + f[i - 1][j][1]) % mod;
                if (i - k - 1 >= 0) c[j] = (c[j] - f[i - k - 1][j][1]) % mod;
                f[i][j][0] = c[j];
                if (j - 1 >= 0) r[i] = (r[i] + f[i][j - 1][0]) % mod;
                if (j - k - 1 >= 0) r[i] = (r[i] - f[i][j - k - 1][0]) % mod;
                f[i][j][1] = r[i];
            }
        }
        return ((f[a][b][0] + f[a][b][1]) % mod + mod) % mod;
    }
};

 

参考资料

  第 129 场力扣夜喵双周赛 - 力扣(LeetCode):https://leetcode.cn/circle/discuss/SZyTo4/view/mv9LwV/

posted @   onlyblues  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
历史上的今天:
2023-04-28 D. Unique Palindromes
Web Analytics
点击右上角即可分享
微信分享提示