Dynamic Programming Solutions for Three Classic Problem Variants
This problem explores a classic dynamic programming approach with interesting variations. The fundamental question involves counting subsequences with certain properties.
When the task is to count substrings, we use a well-established DP formulation:
Define fi,j as the number of valid ways considering the first i characters, where there are j unmatched opening parentheses. The transitions are:
- If si is '(' or '?': fi,j += fi-1,j-1
- If si is ')' or '?': fi,j += fi-1,j+1
For subsequence counting, we introduce an additional transition: fi,j += fi-1,j
Here's the implemantation using prefix sum optimization:
const int MAXN = 5005;
const int MOD = 1000000007;
int dp[MAXN][MAXN];
int prefix[MAXN][MAXN];
char str[MAXN];
int main() {
int len;
scanf("%s", str + 1);
len = strlen(str + 1);
prefix[0][0] = dp[0][0] = 1;
prefix[1][0] = dp[1][0] = 1;
for (int i = 1; i <= len; i++) {
for (int j = 0; j <= len; j++) {
dp[i][j] = dp[i-1][j];
if (j > 0 && (str[i] == '(' || str[i] == '?')) {
dp[i][j] = (prefix[i-1][j-1] + dp[i][j]) % MOD;
}
if (str[i] == ')' || str[i] == '?') {
dp[i][j] = (prefix[i-1][j+1] + dp[i][j]) % MOD;
}
prefix[i][j] = dp[i][j];
}
}
printf("%d\n", prefix[len][0]);
return 0;
}
Problem 2: Optimal Array Rearrangement
This problem involves minimizing the sum of all prefix sums after rearranging elements from multiple arrays into one. When m (the number of operations) is very large, upgrading all element yields maximum benefit.
The key insight is to work backwards: if element a[j] is upgraded at minute j, we subtract the benefit of not having it for those j minutes. This transforms the problem into: given n arrays ai, arrange all elements into a new array b of length Σki such that elements from each original array maintain their relative order, and the sum of prefix sums of b is minimized.
Without order constraints, sorting all elements ascending minimizes the sum of prefix sums. A key lemma states: in array b, placing one segment before another is valid if and only if the average of the first segment is smaller than the average of the second.
Therefore, our strategy is: at each step, select the segment with the smallest average from all arrays and append it to b. Each array should be pre-segmented so that averages within each array are monotonically non-decreasing.
For optimal segmentation, we process each array as follows: when adding an element increases the current segment's average, we start a new segment. Otherwise, we merge with the previous segment. Using a stack-based union-find approach ensures O(n) complexity per array, as each merge reduces the number of segments.
#include <bits/stdc++.h>
using namespace std;
struct Segment {
long long sum;
int cnt;
double avg;
int arrIdx;
int startPos;
int endPos;
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<vector<int>> arr(n);
for (int i = 0; i < n; i++) {
int k;
cin >> k;
arr[i].resize(k);
for (int j = 0; j < k; j++) {
cin >> arr[i][j];
}
}
vector<Segment> allSegments;
for (int i = 0; i < n; i++) {
vector<Segment> localSeg;
long long runningSum = 0;
int count = 0;
for (int val : arr[i]) {
runningSum += val;
count++;
Segment newSeg = {runningSum, count,
(double)runningSum / count, i,
(int)localSeg.size(), (int)localSeg.size()};
while (!localSeg.empty()) {
Segment last = localSeg.back();
double oldAvg = (double)last.sum / last.cnt;
double newAvg = newSeg.sum / newSeg.cnt;
if (oldAvg > newAvg) {
localSeg.pop_back();
newSeg.sum = last.sum + newSeg.sum;
newSeg.cnt = last.cnt + newSeg.cnt;
newSeg.avg = (double)newSeg.sum / newSeg.cnt;
newSeg.startPos = last.startPos;
} else {
break;
}
}
localSeg.push_back(newSeg);
}
for (auto &seg : localSeg) {
allSegments.push_back(seg);
}
}
sort(allSegments.begin(), allSegments.end(),
[](const Segment& a, const Segment& b) {
return a.avg < b.avg;
});
long long result = 0;
long long prefixSum = 0;
for (const auto& seg : allSegments) {
long long segmentSum = seg.sum;
result += prefixSum;
prefixSum += segmentSum;
}
cout << result << endl;
return 0;
}
Problem 3: Interval Counting with Dual Constraints
This problem asks us to count valid intervals [i, j] where both endpoints and values satisfy certain constraints. The structure suggests a 2D point counting approach.
First, we use monotonic stacks to compute for each position i, the maximum extent L[i] it can extend leftward and R[i] it can extend rightward while maintaining valid monotonicity conditions.
For left endpoint constraints, we constructt points (i, Li) and query rectangles [Ri, i] × [n+1, n+1]. Similarly, for right endpoint constraints, we transform coordinates and query the corresponding regions.
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200005;
int n, queryId;
int arr[MAXN];
struct FenwickTree {
int bit[MAXN];
void update(int idx, int delta) {
if (idx == 0) return;
for (; idx < MAXN; idx += idx & -idx) {
bit[idx] += delta;
}
}
int query(int idx) {
int res = 0;
for (; idx > 0; idx -= idx & -idx) {
res += bit[idx];
}
return res;
}
void reset() {
memset(bit, 0, sizeof(bit));
}
} fenwick;
struct Point {
int type;
int x, y;
int value;
int idx;
};
int leftBound[MAXN], rightBound[MAXN];
int Lrange[MAXN], Rrange[MAXN];
Point points[MAXN * 2];
int pointCount;
int answerLeft[MAXN], answerRight[MAXN];
int stackArr[MAXN], stackTop;
bool comparePoints(const Point& a, const Point& b) {
if (a.x != b.x) return a.x < b.x;
if (a.type != b.type) return a.type < b.type;
return a.y < b.y;
}
void computeBoundaries() {
// Lmax: leftmost position with strictly smaller value
stackTop = 0;
for (int i = 1; i <= n; i++) {
while (stackTop > 0 && arr[stackArr[stackTop]] < arr[i]) {
stackTop--;
}
Lrange[i] = stackArr[stackTop];
stackArr[++stackTop] = i;
}
// Lmin: leftmost position with strictly larger value
stackTop = 0;
for (int i = 1; i <= n; i++) {
while (stackTop > 0 && arr[stackArr[stackTop]] > arr[i]) {
stackTop--;
}
leftBound[i] = stackArr[stackTop];
stackArr[++stackTop] = i;
}
for (int i = 1; i <= n; i++) {
Lrange[i] = min(Lrange[i], leftBound[i]);
}
// Rmax and Rmin for right boundaries
stackTop = 0;
stackArr[stackTop] = n + 1;
for (int i = n; i >= 1; i--) {
while (stackTop > 0 && arr[stackArr[stackTop]] < arr[i]) {
stackTop--;
}
rightBound[i] = stackArr[stackTop];
stackArr[++stackTop] = i;
}
stackTop = 0;
stackArr[stackTop] = n + 1;
for (int i = n; i >= 1; i--) {
while (stackTop > 0 && arr[stackArr[stackTop]] > arr[i]) {
stackTop--;
}
Rrange[i] = stackArr[stackTop];
stackArr[++stackTop] = i;
}
for (int i = 1; i <= n; i++) {
Rrange[i] = max(rightBound[i], Rrange[i]);
}
}
void solveRightEndpoint() {
pointCount = 0;
for (int i = 1; i <= n; i++) {
points[++pointCount] = {0, i, Rrange[i] + 1, 0, 0};
}
for (int i = 1; i <= n; i++) {
if (Lrange[i] >= 1) {
points[++pointCount] = {1, Lrange[i], i + 1, 1, i};
}
}
stable_sort(points + 1, points + pointCount + 1, comparePoints);
fenwick.reset();
for (int i = 1; i <= pointCount; i++) {
if (points[i].type == 0) {
fenwick.update(points[i].y, 1);
} else {
answerRight[points[i].idx] +=
points[i].value * fenwick.query(points[i].y);
}
}
}
void solveLeftEndpoint() {
pointCount = 0;
for (int i = 1; i <= n; i++) {
int transformedX = n + 1 - i;
int transformedY = n + 1 - Lrange[i] + 1;
points[++pointCount] = {0, transformedX, transformedY, 0, 0};
if (n + 1 - Rrange[i] >= 1) {
int endX = n + 1 - Rrange[i];
int endY = n + 1 - i + 1;
points[++pointCount] = {1, endX, endY, 1, i};
}
}
stable_sort(points + 1, points + pointCount + 1, comparePoints);
fenwick.reset();
for (int i = 1; i <= pointCount; i++) {
if (points[i].type == 0) {
fenwick.update(points[i].y, 1);
} else {
answerLeft[points[i].idx] +=
points[i].value * fenwick.query(points[i].y);
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> queryId;
for (int i = 1; i <= n; i++) {
cin >> arr[i];
}
computeBoundaries();
solveRightEndpoint();
solveLeftEndpoint();
for (int i = 1; i <= n; i++) {
cout << answerLeft[i] << ' ';
}
cout << '\n';
for (int i = 1; i <= n; i++) {
cout << answerRight[i] << ' ';
}
cout << '\n';
return 0;
}
Note: Using stable_sort proves necessary for passing all test cases, whereas regular sort may produce incorrect results due to subtle ordering dependencies in the coordinate transformation.