PROBLEM LINK:
Practice
Contest: Division 1
Contest: Division 2
Contest: Division 3
Contest: Division 4
Author: iceknight1093
Tester: sushil2006
Editorialist: iceknight1093
DIFFICULTY:
Easy-Med
PREREQUISITES:
None
PROBLEM:
For an array A, we generate a new array B as follows:
- For each i, let k = \min(i, N+1-i).
- Set B_i = \min(B[i-k+1 : i+k-1])
You’re given B, count the number of A that result in this B.
EXPLANATION:
Recall what we did in the solution to the easy version: each element of B puts a constraint on two indices of A: these two indices shouldn’t be larger than B_i, and then possibly one of them should be equal to B_i (depending on how B_i compares to its relevant neighbor).
The issue now is that when N is odd, the constraints don’t nicely split up into disjoint pairs: they can intersect with each other non-trivially.
To deal with this, we’ll use dynamic programming.
First, compute for each index i its upper bound U_i, i.e. the maximum value that A_i is allowed to take.
Also compute R_i, which is the value that should exist at either index i or i+1. This will also be called “the constraint (i, i+1)”.
Note that R_i, if it exists, will be unique: as seen in the easy version, if we need two different elements to appear within the same pair then there’ll be a contradiction anyway.
Let’s now try to fill in values of A from left to right.
Observe that if we’ve filled in values till index i, all the constrained pairs with both elements \leq i must be satisfied; and pairs with both elements \gt i can’t be affected yet anyway.
That leaves only the pair (i, i+1), which may or may not be satisfied.
Noting this, define:
- dp(i, 0) is the number of ways to assign the first i elements, such that the constraint (i, i+1) is satisfied (or doesn’t exist).
- dp(i, 1) is the number of ways to assign the first i elements, such that the constraint (i, i+1) is not satisfied.
Now, to compute these:
- Suppose there’s no constraint (i, i+1).
Then,- If all constraints till i are satisfied, A_i can be anything \leq U_i.
There are dp(i-1, 0) \cdot U_i ways to obtain this, and it should be added to dp(i, 0). - If the constraint (i-1, i) is not satisfied yet, it should be satisfied by A_i.
If U_i \geq R_{i-1}, this can be done and we add dp(i-1, 1) to dp(i, 0); otherwise nothing can be done.
- If all constraints till i are satisfied, A_i can be anything \leq U_i.
- Next, suppose the constraint (i, i+1) does exist.
Then, to compute dp(i, 0):- A_i should definitely equal R_i to satisfy the constraint.
If R_i \gt U_i this is of course impossible. - Next, look at the constraint (i-1, i).
If this is already satisfied/doesn’t exist, we’re good: so we can add dp(i-1, 0) to dp(i, 0). - If it’s not satisfied, then A_i should satisfy it: meaning A_i must equal R_{i-1}.
If R_i = R_{i-1} this is possible and we add dp(i-1, 1) to dp(i, 0); otherwise we do nothing.
- A_i should definitely equal R_i to satisfy the constraint.
- Next, to compute dp(i, 1):
- A_i should not equal R_i.
There are hence U_i - 1 options for it. - Again, we look at the constraint (i-1, i).
If this is satisfied, anything’s fine; so we can add dp(i-1, 0) \cdot (U_i - 1) to dp(i, 1).
If it’s not satisfied, A_i should equal R_{i-1}. This is possible only when U_i \geq R_{i-1} and R_i \neq R_{i-1} (since we’re specifically not satisfying (i, i+1)).
If this is satisfied, add dp(i-1, 1) to dp(i, 1); otherwise do nothing.
- A_i should not equal R_i.
This allows for all the dp values to be computed in linear time.
There are a couple of things to take care of, mainly at indices 1 and N since their values are fixed.
- dp(1, 0) and dp(1, 1) become the base cases, set them appropriately.
- For the final answer, you can use dp(N-1, 0), and add dp(N-1, 1) if R_{N-1} = B_N.
TIME COMPLEXITY:
\mathcal{O}(N) per testcase.
CODE:
Editorialist's code (C++)
// #include <bits/allocator.h>
// #pragma GCC optimize("O3,unroll-loops")
// #pragma GCC target("avx2,bmi,bmi2,lzcnt,popcnt")
#include "bits/stdc++.h"
using namespace std;
using ll = long long int;
mt19937_64 RNG(chrono::high_resolution_clock::now().time_since_epoch().count());
/**
* Integers modulo p, where p is a prime
* Source: Aeren (modified from tourist?)
* Modmul for 64-bit mod from kactl:ModMulLL
* Works with p < 7.2e18 with x87 80-bit long double, and p < 2^52 ~ 4.5e12 with 64-bit
*/
template<typename T>
struct Z_p{
using Type = typename decay<decltype(T::value)>::type;
static vector<Type> MOD_INV;
constexpr Z_p(): value(){ }
template<typename U> Z_p(const U &x){ value = normalize(x); }
template<typename U> static Type normalize(const U &x){
Type v;
if(-mod() <= x && x < mod()) v = static_cast<Type>(x);
else v = static_cast<Type>(x % mod());
if(v < 0) v += mod();
return v;
}
const Type& operator()() const{ return value; }
template<typename U> explicit operator U() const{ return static_cast<U>(value); }
constexpr static Type mod(){ return T::value; }
Z_p &operator+=(const Z_p &otr){ if((value += otr.value) >= mod()) value -= mod(); return *this; }
Z_p &operator-=(const Z_p &otr){ if((value -= otr.value) < 0) value += mod(); return *this; }
template<typename U> Z_p &operator+=(const U &otr){ return *this += Z_p(otr); }
template<typename U> Z_p &operator-=(const U &otr){ return *this -= Z_p(otr); }
Z_p &operator++(){ return *this += 1; }
Z_p &operator--(){ return *this -= 1; }
Z_p operator++(int){ Z_p result(*this); *this += 1; return result; }
Z_p operator--(int){ Z_p result(*this); *this -= 1; return result; }
Z_p operator-() const{ return Z_p(-value); }
template<typename U = T>
typename enable_if<is_same<typename Z_p<U>::Type, int>::value, Z_p>::type &operator*=(const Z_p& rhs){
#ifdef _WIN32
uint64_t x = static_cast<int64_t>(value) * static_cast<int64_t>(rhs.value);
uint32_t xh = static_cast<uint32_t>(x >> 32), xl = static_cast<uint32_t>(x), d, m;
asm(
"divl %4; \n\t"
: "=a" (d), "=d" (m)
: "d" (xh), "a" (xl), "r" (mod())
);
value = m;
#else
value = normalize(static_cast<int64_t>(value) * static_cast<int64_t>(rhs.value));
#endif
return *this;
}
template<typename U = T>
typename enable_if<is_same<typename Z_p<U>::Type, int64_t>::value, Z_p>::type &operator*=(const Z_p &rhs){
uint64_t ret = static_cast<uint64_t>(value) * static_cast<uint64_t>(rhs.value) - static_cast<uint64_t>(mod()) * static_cast<uint64_t>(1.L / static_cast<uint64_t>(mod()) * static_cast<uint64_t>(value) * static_cast<uint64_t>(rhs.value));
value = normalize(static_cast<int64_t>(ret + static_cast<uint64_t>(mod()) * (ret < 0) - static_cast<uint64_t>(mod()) * (ret >= static_cast<uint64_t>(mod()))));
return *this;
}
template<typename U = T>
typename enable_if<!is_integral<typename Z_p<U>::Type>::value, Z_p>::type &operator*=(const Z_p &rhs){
value = normalize(value * rhs.value);
return *this;
}
template<typename U>
Z_p &operator^=(U e){
if(e < 0) *this = 1 / *this, e = -e;
Z_p res = 1;
for(; e; *this *= *this, e >>= 1) if(e & 1) res *= *this;
return *this = res;
}
template<typename U>
Z_p operator^(U e) const{
return Z_p(*this) ^= e;
}
Z_p &operator/=(const Z_p &otr){
Type a = otr.value, m = mod(), u = 0, v = 1;
if(a < (int)MOD_INV.size()) return *this *= MOD_INV[a];
while(a){
Type t = m / a;
m -= t * a; swap(a, m);
u -= t * v; swap(u, v);
}
assert(m == 1);
return *this *= u;
}
template<typename U> friend const Z_p<U> &abs(const Z_p<U> &v){ return v; }
Type value;
};
template<typename T> bool operator==(const Z_p<T> &lhs, const Z_p<T> &rhs){ return lhs.value == rhs.value; }
template<typename T, typename U, typename enable_if<is_integral<U>::value>::type* = nullptr> bool operator==(const Z_p<T>& lhs, U rhs){ return lhs == Z_p<T>(rhs); }
template<typename T, typename U, typename enable_if<is_integral<U>::value>::type* = nullptr> bool operator==(U lhs, const Z_p<T> &rhs){ return Z_p<T>(lhs) == rhs; }
template<typename T> bool operator!=(const Z_p<T> &lhs, const Z_p<T> &rhs){ return !(lhs == rhs); }
template<typename T, typename U, typename enable_if<is_integral<U>::value>::type* = nullptr> bool operator!=(const Z_p<T> &lhs, U rhs){ return !(lhs == rhs); }
template<typename T, typename U, typename enable_if<is_integral<U>::value>::type* = nullptr> bool operator!=(U lhs, const Z_p<T> &rhs){ return !(lhs == rhs); }
template<typename T> bool operator<(const Z_p<T> &lhs, const Z_p<T> &rhs){ return lhs.value < rhs.value; }
template<typename T> bool operator>(const Z_p<T> &lhs, const Z_p<T> &rhs){ return lhs.value > rhs.value; }
template<typename T> bool operator<=(const Z_p<T> &lhs, const Z_p<T> &rhs){ return lhs.value <= rhs.value; }
template<typename T> bool operator>=(const Z_p<T> &lhs, const Z_p<T> &rhs){ return lhs.value >= rhs.value; }
template<typename T> Z_p<T> operator+(const Z_p<T> &lhs, const Z_p<T> &rhs){ return Z_p<T>(lhs) += rhs; }
template<typename T, typename U, typename enable_if<is_integral<U>::value>::type* = nullptr> Z_p<T> operator+(const Z_p<T> &lhs, U rhs){ return Z_p<T>(lhs) += rhs; }
template<typename T, typename U, typename enable_if<is_integral<U>::value>::type* = nullptr> Z_p<T> operator+(U lhs, const Z_p<T> &rhs){ return Z_p<T>(lhs) += rhs; }
template<typename T> Z_p<T> operator-(const Z_p<T> &lhs, const Z_p<T> &rhs){ return Z_p<T>(lhs) -= rhs; }
template<typename T, typename U, typename enable_if<is_integral<U>::value>::type* = nullptr> Z_p<T> operator-(const Z_p<T>& lhs, U rhs){ return Z_p<T>(lhs) -= rhs; }
template<typename T, typename U, typename enable_if<is_integral<U>::value>::type* = nullptr> Z_p<T> operator-(U lhs, const Z_p<T> &rhs){ return Z_p<T>(lhs) -= rhs; }
template<typename T> Z_p<T> operator*(const Z_p<T> &lhs, const Z_p<T> &rhs){ return Z_p<T>(lhs) *= rhs; }
template<typename T, typename U, typename enable_if<is_integral<U>::value>::type* = nullptr> Z_p<T> operator*(const Z_p<T>& lhs, U rhs){ return Z_p<T>(lhs) *= rhs; }
template<typename T, typename U, typename enable_if<is_integral<U>::value>::type* = nullptr> Z_p<T> operator*(U lhs, const Z_p<T> &rhs){ return Z_p<T>(lhs) *= rhs; }
template<typename T> Z_p<T> operator/(const Z_p<T> &lhs, const Z_p<T> &rhs) { return Z_p<T>(lhs) /= rhs; }
template<typename T, typename U, typename enable_if<is_integral<U>::value>::type* = nullptr> Z_p<T> operator/(const Z_p<T>& lhs, U rhs) { return Z_p<T>(lhs) /= rhs; }
template<typename T, typename U, typename enable_if<is_integral<U>::value>::type* = nullptr> Z_p<T> operator/(U lhs, const Z_p<T> &rhs) { return Z_p<T>(lhs) /= rhs; }
template<typename T> istream &operator>>(istream &in, Z_p<T> &number){
typename common_type<typename Z_p<T>::Type, int64_t>::type x;
in >> x;
number.value = Z_p<T>::normalize(x);
return in;
}
template<typename T> ostream &operator<<(ostream &out, const Z_p<T> &number){ return out << number(); }
/*
using ModType = int;
struct VarMod{ static ModType value; };
ModType VarMod::value;
ModType &mod = VarMod::value;
using Zp = Z_p<VarMod>;
*/
// constexpr int mod = 1e9 + 7; // 1000000007
constexpr int mod = (119 << 23) + 1; // 998244353
// constexpr int mod = 1e9 + 9; // 1000000009
using Zp = Z_p<integral_constant<decay<decltype(mod)>::type, mod>>;
template<typename T> vector<typename Z_p<T>::Type> Z_p<T>::MOD_INV;
template<typename T = integral_constant<decay<decltype(mod)>::type, mod>>
void precalc_inverse(int SZ){
auto &inv = Z_p<T>::MOD_INV;
if(inv.empty()) inv.assign(2, 1);
for(; inv.size() <= SZ; ) inv.push_back((mod - 1LL * mod / (int)inv.size() * inv[mod % (int)inv.size()]) % mod);
}
template<typename T>
vector<T> precalc_power(T base, int SZ){
vector<T> res(SZ + 1, 1);
for(auto i = 1; i <= SZ; ++ i) res[i] = res[i - 1] * base;
return res;
}
template<typename T>
vector<T> precalc_factorial(int SZ){
vector<T> res(SZ + 1, 1); res[0] = 1;
for(auto i = 1; i <= SZ; ++ i) res[i] = res[i - 1] * i;
return res;
}
int main()
{
ios::sync_with_stdio(false); cin.tie(0);
auto solve = [&] (auto b) {
int n = size(b);
vector lim(n, INT_MAX);
vector<array<int, 3>> reqs;
reqs.push_back({0, 0, 0});
reqs.push_back({n-1, n-1, n-1});
lim[0] = b[0], lim[n-1] = b[n-1];
{
int x = 1;
for (int i = 1; i <= n/2 - (n%2 == 0); ++i) {
if (b[i] < b[i-1]) return Zp(0);
lim[x] = min(lim[x], b[i]);
lim[x+1] = min(lim[x+1], b[i]);
if (b[i] > b[i-1]) {
reqs.push_back({i, x, x+1});
}
x += 2;
}
x = n-3;
for (int i = n-2; i >= n/2; --i) {
if (b[i] < b[i+1]) return Zp(0);
lim[x] = min(lim[x], b[i]);
lim[x+1] = min(lim[x+1], b[i]);
if (b[i] > b[i+1]) {
reqs.push_back({i, x, x+1});
}
x -= 2;
}
}
vector need(n, 0);
for (auto [i, x, y] : reqs) {
if (lim[x] < b[i] and lim[y] < b[i]) return Zp(0);
if (x == y) continue;
need[x] = b[i];
}
array<Zp, 2> dp{};
if (need[0]) {
if (need[0] == b[n/2]) dp = {1, 0};
else dp = {0, 1};
}
else dp = {1, 0};
for (int i = 1; i+1 < n; ++i) {
array<Zp, 2> ndp{};
if (!need[i]) {
ndp[0] = dp[0]*lim[i];
if (lim[i] >= need[i-1]) ndp[0] += dp[1];
}
else {
ndp[0] = dp[0];
if (need[i] == need[i-1]) ndp[0] += dp[1];
ndp[1] = dp[0]*(lim[i] - 1);
if (lim[i] >= need[i-1] and need[i] != need[i-1]) ndp[1] += dp[1];
}
dp = ndp;
}
Zp ans = dp[0];
if (need[n-2] == b[n-1]) ans += dp[1];
return ans;
};
int t; cin >> t;
while (t--) {
int n; cin >> n;
vector b(n, 0);
for (int &x : b) cin >> x;
cout << solve(b) << '\n';
}
}