Skip to content

[CodeChef] Starters 79 — K-Beautiful Subarrays: two bucket sizes, two stars-and-bars

Published 30 September 2026

Problem: CodeChef — K-Beautiful Subarrays (Starters 79, difficulty 2753). Official editorial: KBEAUTIFUL editorial.

I haven’t finished this one. I got the structure quickly — the array has to be periodic, there is a forced minimum cost, and the leftover operations are what you count — and then I stalled on the counting itself. I knew it was stars and bars, and I could not see how to apply stars and bars to an inequality whose coefficients aren’t all 1. This post is that one step, worked slowly on small numbers, plus a TODO for the rest.

The problem

You’re given an array \(A\) of size \(N\) and may perform at most \(M\) operations, each one increasing a single element by \(1\). An array is \(K\)-beautiful if every subarray of size \(K\) has the same sum. Count the distinct \(K\)-beautiful final arrays you can reach, modulo \(998244353\).

Constraints: \(1 \le K \le N \le 10^6\), \(1 \le M \le 10^6\), \(A_i \le 10^{18}\), and the sums of \(N\) and of \(M\) over all test cases are each at most \(10^6\).

Equal window sums means periodic

Two neighbouring windows share everything except their end elements:

\[A_i + A_{i+1} + \cdots + A_{i+K-1} \;=\; A_{i+1} + \cdots + A_{i+K-1} + A_{i+K} \quad\Longleftrightarrow\quad A_i = A_{i+K}.\]

So \(K\)-beautiful just means every residue class of positions mod \(K\) holds a single value. I’ll call a residue class a bucket: there are \(K\) of them, and the final array is completely described by one number per bucket.

The forced cost

Operations only increase, so a bucket can’t end below its current maximum. Raising every element of a bucket to that maximum costs

\[\sum_{i \in \text{bucket}} \big(\max(\text{bucket}) - A_i\big),\]

and nothing cheaper makes the bucket constant. Add this up over all \(K\) buckets to get the baseline. If the baseline exceeds \(M\) the answer is \(0\). Otherwise

\[R = M - \text{baseline}\]

operations are left over, and they’re optional.

What the leftover buys

After the baseline every bucket is already constant. The only thing left to do is raise a whole bucket further, and raising a bucket of size \(c\) by \(1\) costs \(c\) operations, because each of its elements has to move together.

If bucket \(j\) has size \(c_j\) and I raise it by \(x_j \ge 0\), the spend is \(\sum_j c_j x_j\). Different tuples \((x_1, \ldots, x_K)\) give different final arrays, and every reachable final array comes from exactly one tuple. So the answer is the number of non-negative integer solutions of

\[c_1 x_1 + c_2 x_2 + \cdots + c_K x_K \;\le\; R.\]

Only two bucket sizes exist

This is the fact that makes it tractable. Write

\[q = \left\lfloor \frac{N}{K} \right\rfloor, \qquad t = N \bmod K.\]

Then exactly \(t\) buckets have size \(q + 1\) (the first \(t\) residues, which get one extra element), and the other \(K - t\) buckets have size \(q\). There are no other sizes. Since \(K \le N\), \(q \ge 1\).

So the \(K\) coefficients collapse into two groups, and the inequality is really

\[q\,(x_1 + \cdots + x_{K-t}) \;+\; (q+1)\,(y_1 + \cdots + y_t) \;\le\; R,\]

with the \(x\)’s being the raises of the size-\(q\) buckets and the \(y\)’s the raises of the size-\((q+1)\) buckets.

Where I got stuck

Stars and bars counts solutions of \(z_1 + \cdots + z_g = S\): there are

\[\binom{S + g - 1}{g - 1}\]

of them. Every coefficient is \(1\). My inequality has coefficients \(q\) and \(q + 1\), and I kept trying to find a single stars-and-bars expression for the whole thing. There isn’t one.

What works is to stop looking at the individual variables and name the two group totals:

\[X = x_1 + \cdots + x_{K-t}, \qquad Y = y_1 + \cdots + y_t.\]

The inequality only ever sees \(X\) and \(Y\):

\[qX + (q+1)Y \le R.\]

And inside a group every coefficient is the same, so once \(X\) is fixed, “how do I split \(X\) among the \(K - t\) small buckets” is plain stars and bars, with no coefficients in sight. Same for \(Y\) among the \(t\) big buckets. The two splits are independent, so they multiply:

\[\text{answer} \;=\; \sum_{\substack{X,\,Y \,\ge\, 0 \\ qX + (q+1)Y \,\le\, R}} \binom{X + (K-t) - 1}{(K-t) - 1} \binom{Y + t - 1}{t - 1}.\]

So it’s two stars-and-bars, one per bucket size, glued together by enumerating the pair of totals. The coefficients never enter a binomial; they only decide which pairs \((X, Y)\) are allowed.

One edge: when \(t = 0\) there are no big buckets, so \(Y\) must be \(0\) and its factor is \(1\). The small group is never empty, since \(t < K\).

Example 1: three buckets

Take \(N = 5\), \(K = 3\), and suppose \(R = 4\) operations are left after the baseline. Then \(q = 1\), \(t = 2\):

  • positions \(\{1, 4\}\) — size 2
  • positions \(\{2, 5\}\) — size 2
  • position \(\{3\}\) — size 1

Call the raises \(u, v\) for the two size-2 buckets and \(w\) for the size-1 bucket. The constraint is \(2u + 2v + w \le 4\). Listing by hand, for each \((u, v)\) count the \(w\)’s that still fit:

\(u\) \(v\) spent \(2u + 2v\) possible \(w\) count
0 0 0 0, 1, 2, 3, 4 5
0 1 2 0, 1, 2 3
1 0 2 0, 1, 2 3
0 2 4 0 1
1 1 4 0 1
2 0 4 0 1

Total \(14\).

Now the formula. Here \(X = w\) (one small bucket) and \(Y = u + v\) (two big buckets), with \(X + 2Y \le 4\):

\(Y\) ways to split \(Y\): \(\binom{Y+1}{1}\) allowed \(X\) ways per \(X\): \(\binom{X}{0}\) contribution
0 1 0..4 1 \(1 \cdot 5 = 5\)
1 2 0..2 1 \(2 \cdot 3 = 6\)
2 3 0 1 \(3 \cdot 1 = 3\)

\(5 + 6 + 3 = 14\). The rows of the first table with the same \(u + v\) are exactly what \(\binom{Y+1}{1}\) is counting: \(Y = 1\) is the two rows \((0,1), (1,0)\), and \(Y = 2\) is the three rows \((0,2), (1,1), (2,0)\).

Example 2: ten buckets

With more buckets the hand enumeration stops being possible and the formula doesn’t change. Take \(N = 23\), \(K = 10\), \(R = 4\). Then \(q = 2\), \(t = 3\): seven buckets of size 2 and three of size 3.

\[2\,(x_1 + \cdots + x_7) + 3\,(y_1 + y_2 + y_3) \le 4 \quad\Longrightarrow\quad 2X + 3Y \le 4.\]

\(Y\) can only be \(0\) or \(1\). If \(Y = 0\) then \(X \in \{0, 1, 2\}\); if \(Y = 1\) then \(X = 0\). Four pairs:

\((X, Y)\) \(\binom{X+6}{6}\) \(\binom{Y+2}{2}\) product
\((0, 0)\) 1 1 1
\((1, 0)\) 7 1 7
\((2, 0)\) 28 1 28
\((0, 1)\) 1 3 3

Total \(39\).

The \(28\) is worth unpacking once, because it’s the binomial doing real work: \(X = 2\) spread over seven buckets is either a \(2\) in one bucket (\(7\) ways) or a \(1\) in each of two buckets (\(\binom{7}{2} = 21\) ways), and \(7 + 21 = 28 = \binom{8}{6}\).

Checking against sample 1

The first sample is \(N = 5\), \(M = 4\), \(K = 3\), \(A = [1, 2, 1, 2, 1]\), expected answer \(5\).

Buckets: \(\{A_1, A_4\} = \{1, 2\}\) costs \(1\) to level, \(\{A_2, A_5\} = \{2, 1\}\) costs \(1\), \(\{A_3\}\) costs \(0\). Baseline \(2\), so \(R = 2\), and with the same shape as Example 1 the constraint is \(X + 2Y \le 2\):

  • \(Y = 0\): \(X \in \{0, 1, 2\}\), contribution \(1 \cdot 3 = 3\)
  • \(Y = 1\): \(X = 0\), contribution \(2 \cdot 1 = 2\)

\(3 + 2 = 5\), matching the five arrays listed in the statement.

The double sum as code

A direct transcription of the formula. It reproduces all five sample answers, and it agrees with a brute force over every tuple of raises on a few thousand small random cases.

import sys
from math import comb

MOD = 998244353

def ways(total, groups):
    # non-negative solutions of z_1 + ... + z_groups = total
    if groups == 0:
        return 1 if total == 0 else 0
    return comb(total + groups - 1, groups - 1)

def solve(n, m, k, a):
    baseline = 0
    for r in range(k):
        bucket = a[r::k]
        baseline += len(bucket) * max(bucket) - sum(bucket)
    if baseline > m:
        return 0
    rem = m - baseline

    q, t = divmod(n, k)
    small, big = k - t, t               # buckets of size q, of size q + 1

    ans = 0
    for y in range(rem // (q + 1) + 1):
        for x in range((rem - (q + 1) * y) // q + 1):
            ans += ways(x, small) * ways(y, big)
    return ans % MOD

def main():
    data = sys.stdin.buffer.read().split()
    p = 1
    out = []
    for _ in range(int(data[0])):
        n, m, k = int(data[p]), int(data[p + 1]), int(data[p + 2]); p += 3
        a = [int(v) for v in data[p:p + n]]; p += n
        out.append(solve(n, m, k, a))
    print("\n".join(map(str, out)))

main()

This is correct and far too slow. The number of \((X, Y)\) pairs is about \(\dfrac{R^2}{2q(q+1)}\), and when \(K > N/2\) we have \(q = 1\), so \(R = 10^6\) gives roughly \(2.5 \cdot 10^{11}\) pairs — before even counting the cost of exact comb on big arguments.

TODO

  • Collapse the inner loop. For a fixed \(Y\), \(X\) runs over a full prefix \(0..A\) with \(A = \left\lfloor \frac{R - (q+1)Y}{q} \right\rfloor\), and the hockey-stick identity \(\sum_{X=0}^{A} \binom{X + g - 1}{g - 1} = \binom{A + g}{g}\) should turn it into one binomial. That would leave a single sum over \(Y\) with about \(R/(q+1)\) terms, which fits in the \(\sum M \le 10^6\) budget. Work through why the identity holds (it’s “at most \(A\) split among \(g\) buckets” with a slack bucket added) instead of just using it.
  • Re-do Example 1 with the single sum and confirm it still gives \(14\), to be sure the collapse doesn’t lose the split between buckets of the same size.
  • Precompute factorials and inverse factorials mod \(998244353\) so each binomial is \(O(1)\). The largest top argument is about \(R + K \le M + N\).
  • Write the fast version, test it against the double sum above on random small cases, and submit.
  • Read the editorial’s derivation and compare.

The lesson so far. Stars and bars needs every coefficient to be \(1\). When a constraint has a few distinct coefficients, group the variables by coefficient, enumerate the group totals, and use stars and bars inside each group. The coefficients pick which totals are allowed; the binomials count the splits.