Arithmetic Operations(分类暴力dp)

Arithmetic Operations

[Link](Problem - E - Codeforces)

题意

给你一个数组 a a a,每次操作可以选择一个 a i a_i ai将其变成任意一个数 x x x,问你最少操作多少次使之变成一个等差数列。

思路

​ 不好判断修改哪几个数,但是哪些数不变比较好判断。我们可以枚举公差 d d d,首先只考虑 d > = 0 d>=0 d>=0的情况对于 d < 0 d<0 d<0的情况,将原数组反转即可,对于每个 d d d我们让 a i − ( i − 1 ) ∗ d a_i-(i-1)*d ai(i1)d,相当于把偏移量减去,之后剩下的最多的就是我们要留下数,剩下的都是需要改变的。设 m = m a x ( a i ) m=max(a_i) m=max(ai),我们的公差不会超过这个数,例如 a 1 , a 2 , a 3 a_1,a_2,a_3 a1,a2,a3三个数 m = a 2 m=a_2 m=a2,如果 d < = m d<=m d<=m则我们最多只需要改变 a 1 , a 3 a_1,a_3 a1,a3,若 d > m d>m d>m则必须修改 a 1 , a 2 , a 3 a_1,a_2,a_3 a1,a2,a3

​ 但是这样枚举复杂度为 O ( n m ) O(nm) O(nm)太高了,考虑分类对于 d < m d<\sqrt m d<m 的我们枚举 d d d,复杂度为 O ( n m ) O(n\sqrt m) O(nm )

​ 对于 d ≥ m d\ge \sqrt m dm 情况,首先我们来证任意一个 a i a_i ai最多可能和 a i + m a_{i+\sqrt m} ai+m 一起保留,假设 j > i + m j>i+\sqrt m j>i+m ,则 a j − a i ≥ d × m > m a_j-a_i\ge d\times \sqrt m >m ajaid×m >m,由于 m a x ( a i ) = = m max(a_i)==m max(ai)==m因此显然不对,所以 i i i j > i + m j>i+\sqrt m j>i+m 的数只能保留一个。由于要让一些数有相同的 d d d,因此将 i → j ( j ≤ i + m ) i\to j(j\le i+\sqrt m) ij(ji+m )连一条边,边权为 a j − a i j − i = d \frac{a_j-a_i}{j -i}=d jiajai=d(如果可以整除),最多会有 n m n\sqrt m nm 条边,我们相当于要找最多的具有相同边权的边,由于是个拓扑图,可以直接用 f [ i ] [ j ] : 以 i 结 尾 的 为 d 的 边 有 多 长 f[i][j]:以i结尾的为d的边有多长 f[i][j]:id,转移即可,复杂度为 O ( n m ) O(n\sqrt m) O(nm )

​ 由于第二部分的图比较稀疏,因此实际上以小于 m \sqrt m m 来分类更优一些。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int n, m, k;
int S = 200;
int a[N];
int b[N * 200];
int solve() {
    int res = 0;
    for (int d = 0; d < S; d ++) {
        for (int i = 0; i < n; i ++) {
            b[a[i] + d * (n - i)] ++;
            res = max(res, b[a[i] + d * (n - i)]);
        }
        for (int i = 0; i < n; i ++) 
            b[a[i] + d * (n - i)] --;
        
    }

    map<int, int> f[n + 1];

    for (int i = 0; i < n; i ++)
        for (int j = max(0, i - N / S); j < i; j ++) {
            int d = (a[i] - a[j]) / (j - i);
            int r = (a[i] - a[j]) % (j - i);
            if (d >= S && !r) {
                f[i][d] = max(f[i][d], f[j][d] + 1);
                res = max(res, f[i][d] + 1);
            }
        }

    return res;
}
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n;
    for (int i = 0; i < n; i ++) cin >> a[i];
    int ans = solve();
    reverse(a, a + n);
    ans = max(ans, solve());
    cout << n - ans << '\n';
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值