Suppose that we can compute the answers for all prefixes of the input array in T time. Then computing the answer for all contiguous subarrays of the input array can be done in O(NT) time, and answering the queries takes O(Q) additional time.
For all subtasks, we use dynamic programming.
Subtask 1: T=O(NB)
For each i∈[0,N] and x∈[0,B], let dp[i][x] denote the number of ways to form x after moving over papers 1…i. The answer for the prefix 1…i is ∑Bj=Adp[i][j].
Next, we describe how to compute all of these DP states. We initialize dp[0][0]=1 and use the following reasoning to generate dp[i] from dp[i−1]. Suppose our pile currently has size s, has value k when read from top to bottom, and that the cows are considering adding digit ai to the pile.
Since each DP transition runs in constant time, computing all these DP states takes time proportional to the number of DP states, which is O(NB).
Jesse Choe's code:
#include <bits/stdc++.h> using namespace std; const int MOD = 1e9 + 7; int countDigits(int x) { int ans = 0; while (x) { ans++, x /= 10; } return ans; } int main() { int n; long long a, b; cin >> n >> a >> b; int dp[n][n + 1][b + 1]{}; vector<int> d(n); for (int i = 0; i < n; i++) cin >> d[i]; for (int i = 0; i < n; i++) { dp[i][i][0] = 1; } for (int i = 0; i < n; i++) { for (int j = i; j < n; j++) { for (int k = 0; k <= b; k++) { dp[i][j + 1][k] += dp[i][j][k], dp[i][j + 1][k] %= MOD; int addRight = 10 * k + d[j]; if (addRight <= b) { dp[i][j + 1][addRight] += dp[i][j][k]; dp[i][j + 1][addRight] %= MOD; } int addLeft = pow(10, countDigits(k)) * d[j] + k; if (addLeft <= b) { dp[i][j + 1][addLeft] += dp[i][j][k]; dp[i][j + 1][addLeft] %= MOD; } } } } for (int i = 0; i < n; i++) { for (int j = i + 1; j <= n; j++) { for (int k = 1; k <= b; k++) dp[i][j][k] += dp[i][j][k - 1]; } } int q; cin >> q; for (int i = 0; i < q; i++) { int l, r; cin >> l >> r; --l; cout << (dp[l][r][b] - dp[l][r][a - 1] + MOD) % MOD << endl; } }
Subtask 2: A=B, T=O(N(log10B)2)
Let d be the number of digits in B. For each i∈[1,N] and 1≤l≤r≤d, let dp[i][l][r] denote the number of ways to form Bl…r (the integer formed by concatenating the lth through rth digits of B) after moving over papers 1…i. There are O(Nd2) such states, each of which can be computed in O(1) time from dp[i−1][l][r], dp[i−1][l+1][r], and dp[i][l][r−1]. The answer for prefix 1…i is dp[i][1][d].
Full credit: T=O(N(log10B)2)
We subtract the number of ways to form an integer at most A−1 from the number of ways to form an integer at most B. To count the number of ways to form an integer at most B, we augment our DP states from the previous subtask with an additional flag that is equal to 0, 1, or 2. Then dp[i][l][r][0], dp[i][l][r][1], and dp[i][l][r][2] denote the number of ways to form an r−l+1-digit integer less than, equal to, or greater than Bl…r after moving over papers 1…i, respectively. The DP transitions are similar to the previous subtask. The answer for prefix 1…i is dp[i][1][d][1]+dp[i][1][d][0]+∑dj=2∑2k=0dp[i][j][d][k]. Here, dp[i][1][d][1] is the number of ways to make a d-digit number equal to B, dp[i][1][d][0] is the number of ways to make a d-digit number less than B, and ∑2k=0dp[i][j][d][k] is the number of ways to make a (d−j+1)-digit number.
Danny Mittal's code:
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.StringTokenizer; public class Milkshake { public static final long MOD = 1000000007; public static void main(String[] args) throws IOException { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer tokenizer = new StringTokenizer(in.readLine()); int n = Integer.parseInt(tokenizer.nextToken()); long a = Long.parseLong(tokenizer.nextToken()); long b = Long.parseLong(tokenizer.nextToken()); char[] digits = in.readLine().replace(" ", "").toCharArray(); long[][] answersLeft = solve(("" + (a - 1L)).toCharArray(), digits); long[][] answersRight = solve(("" + b).toCharArray(), digits); StringBuilder out = new StringBuilder(); for (int q = Integer.parseInt(in.readLine()); q > 0; q--) { tokenizer = new StringTokenizer(in.readLine()); int l = Integer.parseInt(tokenizer.nextToken()) - 1; int r = Integer.parseInt(tokenizer.nextToken()) - 1; long answer = answersRight[l][r] - answersLeft[l][r]; answer += MOD; answer %= MOD; out.append(answer).append('\n'); } System.out.print(out); } static long[][] solve(char[] limit, char[] digits) { long[][] answers = new long[digits.length][digits.length]; for (int j = 0; j < digits.length; j++) { long[][][] dp = new long[limit.length][limit.length][3]; for (int k = j; k < digits.length; k++) { for (int x = 0; x < limit.length; x++) { for (int y = limit.length - 1; y > x; y--) { if (digits[k] > limit[x]) { for (int c = 0; c <= 2; c++) { dp[x][y][2] += dp[x + 1][y][c]; } } else if (digits[k] == limit[x]) { for (int c = 0; c <= 2; c++) { dp[x][y][c] += dp[x + 1][y][c]; } } else { for (int c = 0; c <= 2; c++) { dp[x][y][0] += dp[x + 1][y][c]; } } dp[x][y][2] += dp[x][y - 1][2]; dp[x][y][compare(digits[k], limit[y])] += dp[x][y - 1][1]; dp[x][y][0] += dp[x][y - 1][0]; for (int c = 0; c <= 2; c++) { dp[x][y][c] %= MOD; } } } for (int x = 0; x < limit.length; x++) { dp[x][x][compare(digits[k], limit[x])] += 2; } for (int x = 0; x < limit.length; x++) { answers[j][k] += dp[x][limit.length - 1][0]; answers[j][k] += dp[x][limit.length - 1][1]; if (x != 0) { answers[j][k] += dp[x][limit.length - 1][2]; } answers[j][k] %= MOD; } } } return answers; } static int compare(char a, char b) { return Integer.signum(a - b) + 1; } }