CF1928E: Modular Sequence 题解
E:
题意:你需要构造长度为 n 的数列 a ,满足和为sum,且 \(a_1=x\);$a_i=a_{i-1}+y $ 或 \(a_{i-1}\mod y\) 。(n,sum,x,y<=2e5)
先说下我曲折的做题经历。不想看可以直接跳到solution。
题目很容易让人想到复杂度和根号有关。为了简化描述,先把题意转化为标准形式,即:已知 \(a_1\) ,每次可以加一或者归零,构造和为 sum 的数列。
我寻思 \(\frac {sum^2}{log}\) 应该可过?于是想了个bitset背包的做法,由于要涉及到当前值,我设 \(f[i][j]\) 表示最后一个数字是 i ,能否使和为 j。枚举第一次归零到最后的长度,用 sum 减去 \(a_1\) 为首的等差数列就是 j 。然后却卡在了方案输出上,我一开始以为,由于靠前位置的 \(f[i][j]\) 是当前位置 \(f[i][j]\) 的子集,所以假如 \(f[i-1][j-i]\) 为1,我就可以让最后一个数字是 i 。后来发现这个贪心是错的,因为你无法确定 \(f[i][j]\) 这个状态具体有多长,你以为它在通过 \(f[i-1][j-i]\) 不断缩短,但是可能这个状态是要到更长的长度才更新的到。(没看懂很正常,因为这个贪心是我瞎想的)甚至发现不仅正确性没有,复杂度其实是 \(\frac{sum^2\sqrt{sum}}{log}\) 。然后看题解去了。
看到题解说可以根号分治,我发现好有道理:对任意的 sum,一定可以在根号级别就能把 a 构造出来,所以我们只需要考虑 n 小于根号的情况。然后枚举每一位数填什么也改为了枚举下一个三角形的边长。\(f[i][j]\) 表示长度为 i 的数列能不能表示出 j 来。由于 i 最多枚举到根号,因此复杂度为 \(\frac{\sqrt{sum}\sqrt{sum}·sum}{log}\) ,即,枚举位置,枚举下一个三角的边长,对 j 进行背包运算。一交啪叽TLE on test 78。这个复杂度是真不行。于是寻找更靠谱的题解。
Solution:
这是一个比较有启发性的做法。
在上面的做法中一直都在关心某个长度能不能组成 j ,这是由于我们对于输入的 sum,要根据 sum 来算出需要的 j 。但是我们发现拼三角形来达到 j 的这个过程,好像与输入的数据无关,那我们换个思路,预处理出 \(f[j]\) 表示:拼出总和 j 所需的最小长度。放弃使用bitset。
发现问题简化了不少, \(f[j]\) 可以在 \(sum\sqrt{sum}\) 时间预处理。判断有无解时,我们可以枚举 \(a_1\) 开头的等差数列长度,sum 减去开头长度的得到 j ,比较 \(f[j]\) 与剩余部分的长度。
输出方案则可以枚举最后一个三角形边长 i ,若 \(f[j-\frac{(i+1)i}2]\leq f[j]-i\) 说明减掉边长为 i 的三角形后依然有解。这样可以构造出整个序列 a 。
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#define FOR() ll le=e[u].size();for(ll i=0;i<le;i++)
#define QWQ cout<<"QwQ\n";
#define ll long long
#include <vector>
#include <queue>
#include <bitset>
#define ls now<<1
#define rs now<<1|1
using namespace std;
const ll N=201010;
const ll qwq=303030;
const ll inf=0x3f3f3f3f;
inline ll read() {
ll sum = 0, ff = 1; char c = getchar();
while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); }
while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
return sum * ff;
}
ll T;
ll n,S,p,q,base;
ll da=N-10;
ll f[N];
ll ans[N];
inline ll jia(ll shou,ll shu) {
return (shou+shou+shu-1)*shu/2;
}
void qiu() {
memset(f,0x3f,sizeof(f));
f[0] = 0;
for(ll i=0;i<=da;i++) {
for(ll j=2;;j++) {
ll wo = i+jia(0,j);
if(wo>da) break;
f[wo] = min(f[wo],f[i]+j);
}
}
}
void calc(ll len) {
for(ll i=1;i<=n-len;i++) ans[i] = q+i-1;
S -= jia(q,n-len);
ll now = n;
while(S) {
for(ll i=2;i<=len;i++) {
if(S-jia(0,i)>=0 && f[S-jia(0,i)] <= f[S]-i) {
S -= jia(0,i);
for(ll j=i-1;j>=0;j--) ans[now--] = j;
break;
}
}
}
}
int main() {
qiu();
T = read();
while(T--) {
n = read(); q = read(); p = read(); S = read();
for(ll i=1;i<=n;i++) ans[i] = 0;
if(n==1 && S==q) { cout<<"YES\n"<<q<<endl; continue; }
if(n==1 || S<q) { cout<<"NO\n"; continue; }
base = q%p;
S -= n*base;
if(S<0 || S%p) { cout<<"NO\n"; continue; }
S /= p;
q = q/p;
bool flag = 0;
for(ll i=1;i<=n;i++) {
ll wo = S-jia(q,i);
if(wo>=0 && f[wo]<=n-i) { calc(n-i); flag = 1; break; }
}
if(!flag) cout<<"NO\n";
else outing();
}
return 0;
}