E. Modular Sequence
题意
对于一个长度为 n n n 的数组 a a a,定义它是 g o o d good good 的当且仅当:
- a 1 = x a_1 = x a1=x
- a i = a i − 1 + y a_{i} = a_{i - 1} + y ai=ai−1+y 或 a i = a i − 1 m o d y i ≥ 2 a_{i} = a_{i - 1} mod \hspace{3pt} y \quad i \geq 2 ai=ai−1modyi≥2
给定 n 、 x 、 y 、 S n、x、y、S n、x、y、S,问能否构造出一个和为 S S S 的长度为 n n n 的 g o o d good good 数组
思路
注意到后面每个放置的 a i a_i ai,它们模除 y y y 的值都是一样的,因为自从 a 1 = x a_1 = x a1=x 后,后面每次变化都是同余 y y y 的。
定义 r = x m o d y r = x \hspace{3pt} mod \hspace{3pt} y r=xmody,那么我们先将数组的 n n n 个位置填成: a i = r a_i = r ai=r, a 1 = x a_1 = x a1=x,先计算一下这样子最小的数组和是否超过 S S S,超过的话显然没有答案。对于没有超过 S S S 的时候,我们后面肯定是要在 r r r 的基础上放置若干个 y y y,那么剩余要放置的和应该能被 y y y 整除 才行。
假设要放置 l e f t S u m y leftSum \over y yleftSum 个 y y y,我们就一定要尽可能用最短的长度构造出来这么多个 y y y 的放置方式,考虑 D P DP DP,用 d p k dp_k dpk 表示要从空序列开始放置 k k k 个物品,最短需要的长度,那么转移我们可以枚举最后使用的完整上升长度 l e n len len,这个上升长度必须从 0 0 0 开始,长度为 l e n len len,例如: . . . . , 0 , 1 , 2 , . . . , l e n − 1 ...., 0, 1, 2, ..., len - 1 ....,0,1,2,...,len−1 这样一个结尾方式,那么它的子状态就是: d p [ k − l e n ⋅ ( l e n − 1 ) 2 ] dp[k - \frac{len \cdot (len - 1)}{2}] dp[k−2len⋅(len−1)],因为最后这一段上升序列的贡献是 l e n ⋅ ( l e n − 1 ) 2 \frac{len \cdot (len - 1)}{2} 2len⋅(len−1),长度为 l e n len len,我们取 m i n min min 值就可以
对于多组样例,我们只需要关心最后要放置多少个 y y y,所以我们可以预计算 d p dp dp 数组
我们先枚举一开始是否需要先跟着 x x x 递增一段长度,然后再从 0 0 0 开始构造答案,因为我们的 d p dp dp 数组是从 0 0 0 开始构造答案的最短长度,假设一开始从 x x x 开始递增了 k k k 次(不包含 a 1 a_1 a1 ),也就是数组开头为: x , x + y , x + 2 y , . . . , x + k y x, x + y, x + 2y, ..., x + ky x,x+y,x+2y,...,x+ky,这样子的形式,我们得先确保这个 p r e S u m preSum preSum 不会超过 S S S,然后在此基础上计算后面还需要放置的和的余量 l e f t S u m = S − p r e S u m leftSum = S - preSum leftSum=S−preSum,当然这个余量要先预算一下后面每个位置放一个 r r r 的花销, l e f t S u m − = r × ( n − 1 − k ) leftSum -= r \times (n - 1 - k) leftSum−=r×(n−1−k),最后的这个 l e f t S u m leftSum leftSum 不能为负数,并且要能被 y y y 整除,还要要求 d p [ l e f t S u m y ] dp[{leftSum \over y}] dp[yleftSum] 的值小于等于 n − 1 − k n - 1 - k n−1−k,意思是我们剩余的位置必须要足够放置这么多个 y y y,我们才能在这个基础上构造答案
如果上述条件都符合,我们现在中间多余的用不上的位置 [ k + 1 , n − d p [ l e f t S u m y ] ] [k + 1, n - dp[{leftSum \over y}]] [k+1,n−dp[yleftSum]] 先填补上 r r r,然后从 n − d p [ l e f t S u m y ] + 1 n - dp[{leftSum \over y}] + 1 n−dp[yleftSum]+1 这个位置开始构造答案,我们用一个 l e n len len 数组来暂存一下我们的答案构造的上升长度序列,枚举转移的路径即可
时间复杂度: O ( S S ) O(S \sqrt{S}) O(SS)
#include<bits/stdc++.h>
#define fore(i,l,r) for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long
#define ALL(v) v.begin(), v.end()
#define Debug(x, ed) std::cerr << #x << " = " << x << ed;
const int INF=0x3f3f3f3f;
const long long INFLL=1e18;
typedef long long ll;
const int N = 200050;
int dp[N];
void solve(){
int n;
ll x, y, S;
std::cin >> n >> x >> y >> S;
fore(k, 0, n){ //除了首项x外,后面还跟着递增了k次
ll preSum = (k + 1) * x + k * (k + 1) / 2 * y; //前缀和
if(preSum > S) continue;
ll leftSum = S - preSum; //后面需要构造的和
leftSum -= (n - 1 - k) * (x % y); //先减去模数 x % y
if(leftSum < 0 || leftSum % y) continue;
int needSum = leftSum / y; //后面需要多少个y
if(dp[needSum] > n - k - 1) continue; //长度不够构造
std::vector<int> a(n);
a[0] = x; //首项
fore(i, 1, k + 1) a[i] = a[i - 1] + y; //起初跟着长度为k的一个递增
fore(i, k + 1, n - dp[needSum]) a[i] = x % y; //中间这里用不上,我们在 n - dp[needSum] -> n - 1构造
int idx = n - dp[needSum];
std::vector<int> len;
while(needSum){
for(int l = 2; l * (l - 1) / 2 <= needSum; ++l)
if(dp[needSum] == dp[needSum - l * (l - 1) / 2] + l){ //这是一个最短长度的转移
len.push_back(l); //记录构成最短长度的转移
needSum -= l * (l - 1) / 2; //剩余需要放置的y的数量
break;
}
}
for(auto l : len) //按照答案转移放置y
fore(j, 0, l) //从0开始放置,放 l 次
a[idx++] = x % y + j * y; //记住底是 x % y
std::cout << "YES\n";
for(auto el : a) std::cout << el << ' ';
std::cout << endl;
return;
}
std::cout << "NO\n";
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
fore(i, 1, N) dp[i] = INF;
fore(k, 1, N) //dp[i] 表示要放 i 个 y 需要的最短长度是多长
for(int len = 2; len * (len - 1) / 2 <= k; ++len) //这里len是从0开始放的,所以最后一位是len - 1,长度为len
dp[k] = std::min(dp[k], dp[k - len * (len - 1) / 2] + len);
int t;
std::cin >> t;
while(t--){
solve();
}
return 0;
}