题目列表

简单题,但是有个小丑挂掉了。

对于每个 $[10^x,10^{x+1}-1]$ 的区间,计算 $n$ 覆盖了多大范围。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
    freopen("book.in", "r", stdin);
    freopen("book.out", "w", stdout);
    ll n, ans = 0;
    cin >> n;
    ll pw = 1, cnt = 1;
    for (int i = 1; i <= 10; i++)
    {
        if (pw * 10 - 1 <= n)
            ans += cnt * 9 * pw;
        else
        {
            ans += cnt * (n - pw + 1);
            break;
        }
        pw *= 10;
        cnt++;
    }
    cout << ans << endl;
    return 0;
}
/*
[1,9]
[10,99]
[100,999]
...
[10**x,10**(x+1)-1]
*/

我没记错的话我在 cses.fi 上做过这题。但这题本来就很典,估计很多 OJ 都有。

设 $f_i$ 表示凑到 $i$ 的最小步数。

转移 $f_i\gets\min\{f_{i-1},f_{i\times 2},f_{i\times 3}\}+1$。

但是有一个小丑以为 CF 打多了误以为多测只要每个 $\mathcal O(n)$ 就能过,喜提 TLE。

代码

#include <bits/stdc++.h>
using namespace std;
int f[1000020];
void init()
{
    int n = 1000000;
    f[f[f[2] = f[3] = 1] = 0] = 1000;
    for (int i = 4; i <= n; i++)
    {
        int x, y, z;
        x = y = z = INT_MAX;
        x = f[i - 1];
        if (i % 2 == 0)
            y = f[i / 2];
        if (i % 3 == 0)
            z = f[i / 3];
        f[i] = min({x, y, z}) + 1;
    }
}
void solve()
{
    int n;
    cin >> n;
    cout << f[n] << '\n';
}
int main()
{
    freopen("number.in", "r", stdin);
    freopen("number.out", "w", stdout);
    init();
    int t;
    cin >> t;
    while (t--)
        solve();
    return 0;
}

一眼 dp,状态很容易设出 $f_{i,0/1/2}$ 表示第 $i$ 个位置往左不动往右的答案。

转移需要分讨。

  • 当前有盖子

    • 左边有盖子

      $f_{i,0}\gets f_{i-1,0}+a_{i-1}$

      $f_{i,1}\gets \max\{f_{i-1,0},f_{i-1,1}\}+a_{i}$

      $f_{i,2}\gets\max\{f_{i-1,0},f_{i-1,1},f_{i-1,2}\}+a_{i+1}$

    • 反之

      $f_{i,0}\gets f_{i-1,0}+a_{i-1}$

      $f_{i,1}\gets f_{i-1,1}+a_{i}$

      $f_{i,2}\gets f_{i-1,1}+a_{i+1}$

  • 反之

    $f_{i,0}\gets\max\{f_{i-1,0},f_{i-1,1}\}$

    $f_{i,1}\gets\max\{f_{i-1,0},f_{i-1,1},f_{i-1,2}\}$

    这里不需要转移 $f_{i,2}$,它本来就没有盖子,无需转移。转移 $f_{i,0}$ 是为了让之后的 $f_{i+1,0}$ 能够转移。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll f[200020][3];
int n;
ll a[200020];
int b[200020];
//f[i][0/1/2] left,mid,right
int main()
{
    freopen("box.in", "r", stdin);
    freopen("box.out", "w", stdout);
    cin >> n;
    for (int i = 1; i <= n; i++)
        scanf("%lld", a + i);
    for (int i = 1; i <= n; i++)
        scanf("%d", b + i);
    for (int i = 1; i <= n; i++)
    {
        if (b[i]) //有盖子
        {
            f[i][0] = f[i - 1][0] + a[i - 1];
            if (b[i - 1]) //左边也有盖子
            {
                f[i][1] = max(f[i - 1][0], f[i - 1][1]) + a[i];
                f[i][2] = max({f[i - 1][0], f[i - 1][1], f[i - 1][2]}) + a[i + 1];
            }
            else
            {
                f[i][1] = f[i - 1][1] + a[i];
                f[i][2] = f[i - 1][1] + a[i + 1];
            }
        }
        else
        {
            f[i][0] = max(f[i - 1][0], f[i - 1][1]);
            f[i][1] = max({f[i - 1][0], f[i - 1][1], f[i - 1][2]});
        }
    }
    cout << max(f[n][0], f[n][1]) << endl;
    return 0;
}

很厉害的一道题!

首先考虑暴力怎么写。我说的不是 $3^n$ 级别的,如果你只想到这一层建议先想出多项式复杂度的代码再看。

我们的暴力应该是 $\mathcal O(Vn^3)$。

暴力的写法,应该是 $\mathcal O(n)$ 枚举更改哪个位置,$\mathcal O(V)$ 枚举更改成什么结果,$\mathcal O(n^2)$ 枚举子序列计算。

考虑优化哪一个。$O(n^2)$ ,枚举子序列大抵是可以优化的,但我不会。

考虑优化 $O(V)$ 枚举更改值。

显然的,由于只有 $300$ 个更改方案,所以能构成的完全平方数不会过多,大约是根号级别的。

那么就很好优化了,$\mathcal O(n)$ 枚举修改哪个位置,先算出它没有干扰到哪些子序列,这些子序列会产生多少贡献,这个是很好算的。

然后,我们算出更改这个位置之后的某个子序列上界和下界,这里记作 $L$ 和 $R$。

$[L,R]$ 之间的完全平方数 $p^2$ 不会过多,我们枚举可行的 $p$。$p$ 的范围应为 $[\left\lceil\sqrt L\right\rceil,\left\lfloor\sqrt R\right\rfloor]$。找完全平方数选择枚举它的因子降低复杂度是一个很典的 trick。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
template <typename T>
void gmx(T &x, T y) { x = max(x, y); }
int n;
int a[320];
int s[320];
bool sq[100020];
ll f[320];
ll ans;
int S(int l, int r) { return s[r] - s[l - 1]; }
int main()
{
    freopen("sum.in", "r", stdin);
    freopen("sum.out", "w", stdout);
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i], s[i] = s[i - 1] + a[i];
    for (int i = 1; i <= 300; i++)
        sq[i * i] = 1;
    for (int i = 1; i <= n; i++) //修改第i个位置
    {
        int tmp = 0;
        for (int l = 1; l <= n; l++)
            for (int r = l; r <= n; r++)
                if (i < l || r < i)
                    if (sq[S(l, r)])
                        tmp++;
        memset(f, 0, sizeof(f));
        for (int l = 1; l <= n; l++)
        {
            for (int r = l; r <= n; r++)
            {
                if (i < l || r < i)
                    continue;
                int L = 1, R = 300;
                int sum = S(l, r) - a[i];
                L += sum;
                R += sum;
                // cout << L << " " << R << endl;
                int mayL = ceil(sqrt(L)), mayR = sqrt(R);
                // cout << mayL << " " << mayR << endl;
                // cout << endl;
                for (int k = mayL; k <= mayR; k++)
                    if (k * k - sum <= 300)
                        f[k * k - sum]++;
            }
        }
        for (int i = 1; i <= 300; i++)
            gmx(ans, f[i] + tmp);
    }
    cout << ans << endl;
    return 0;
}

最后修改:2023 年 11 月 07 日
v我50吃疯狂星期四