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;
}
posted @ 2024-02-27 20:08  maple276  阅读(7)  评论(0编辑  收藏  举报