题目简略描述:给定正整数,长为的正整数数列满足限制,记的个数,求

解答:记,利用集合的无序性,有如下引理

引理1:任意,都能找到一个(除去0之后)单调递减的同样有.

不妨设单调递增,由于单调递增,由排序不等式,上式,其中单调递减.此时只改变了的顺序,换言之,不变(),因此仍然保持,但是求和比原来小了,由于单调递减且,有,那么将多出的加到最大值上即可得到一个同样符合求和为,此时对应的满足去0后单调递减,或者说去掉索引为后单调递减

以上引理表明,我们总能认为是单调递减的,因为其他情况都可以转换到此且mex不变.最优解的也是如此。并且,由于我们排序后只改变了最大元素,因此或者的形状

我们考虑,这等价于。我们发现中大于的元素都是没用上的(如果有的话)。如果有大于的元素,由于去0后单调递减,那么有,将数量减少到,并类似的将多的加到最大元素上,于是得到的构造,再利用引理做排序,如果还有大于的元素,重复上述过程。

最终我们得到中没有大于的元素,换言之,,也即。而由于单调递减,于是可以断定,其中,而第个元素为,且.因此,我们对的RHS求和得到

并且.基于此基本就可以做二分法了,对在值域上二分,判断得到的是否大于等于,符合就表明此时的k是一个可行解。

(这里能二分的原因是上面构造保证了小于最优解的构造都存在;或者说二分判断条件是单调的)

提供一个二分的代码:

int main() {
    int t;
    cin >> t;
    while (t--) {
        long long m;
        cin >> m;
        unsigned long long ans = 0;
        long long l = 1, r = 2e6;
        long long mid = (l + r) / 2;
        while (l +1< r) {
            mid = (l + r) / 2;
            long long a = m - (mid - 1) * (mid - 2) * (mid + 3) / 6;
            if (a >= mid - 1) {
                ans = mid;
                l = mid;
            } else r = mid;
        }
        cout << ans << endl;
    }
}

其中有需要注意的一些点,一个显然的是a有负值,不能用unsigned;另外一个比较关键的是r的估计,由于,从上面的计算可以知道,显然是在1e6量级的,但是如果直接r=1e6过不了。当然可以调整出r=2e6可以过,不过我们也可以做一个更好的估计然后不使用二分,直接遍历:

,我们可以得到的一个估计:

于是。有了这个估计就可以直接遍历

int main() {
    int t;
    cin >> t;
    while(t--){
    long long m;
    cin >> m;
    long long k=cbrt(6*m)+5;
    while(true){
        long long a = m - (k-1)*(k-2)*(k+3)/6;
        if(a>=k-1) break;
        k--;
    }
    cout << k << endl;
    }
}

当然,如果注意到上界,还能做到的算法,如果足够大,可以分配给1~k-1使得其都多一个,并且自己还能保留大于等于的数量,那么就得到了的构造。因此,最优的情况应该要求 于是就有

于是,综合得到,可以计算m在R上区间长度值域在之间,因此可以做到11次判断就得到答案(比二分的略少一些并且不用管的大小)

将先前二分的l,r改为l = cbrt(6*(m-1))-3, r = cbrt(6*m)+6即可。

此外,顺带一提,手动测试了一下,l = cbrt(6*(m-1))-1, r = cbrt(6*m)+2也能AC本题,区间长度减少了6,只是不确定是不是严格的上下界,可能是数据比较弱(?)