Codeforces Round #688 (Div. 2) CF1453F. Even Harder dp及优化

CF Round 688(Div. 2) F. Even Harder

在这里插入图片描述
在这里插入图片描述

题目链接:https://codeforces.com/contest/1453/problem/F

n n n个位置,每个位置有一个权值 a i a_i ai 0 ≤ a i ≤ n − i 0\le a_i\le n - i 0aini

当你在第 i i i个位置时,你可以移动到 [ i + 1 , i + a i ] [i+1,i+a_i] [i+1,i+ai]位置中的某一个(也就是说 a i > 0 a_i\gt 0 ai>0才能移动)。

最后移动到 n n n则游戏胜利。

此时可能有多种游戏胜利的走法,我希望这个游戏只能由一种胜利的走法。

现在你可以将某些位置的权值改变为 0 0 0,问最少改变几个位置,使得游戏只有一种胜利的走法。

注意:合法的路径中经过的位置除了 a n a_n an外不能有 0 0 0

2 ≤ n ≤ 3000 2\le n\le 3000 2n3000

样例:
3
4
1 1 1 0
5
4 3 2 1 0
9
4 1 4 2 1 0 2 1 0
---
0
3
2

思路

思路参考:https://www.cnblogs.com/Heltion/p/14088245.html

希望能尽量讲清楚,大佬的思路确实很厉害。


如果唯一路径是 u 1 = 1 , u 2 , ⋯ , u m = n u1=1,u2,⋯,um=n u1=1,u2,,um=n,那么要满足 u i + 1 ≤ u i + a u i < u i + 2 u_{i+1}≤u_i+a_{ui}<u_{i+2} ui+1ui+aui<ui+2,并且对于所有 u i < j < u i + 1 , j + a j < u i + 1 u_i<j<u_{i+1},j+a_j<u_{i+1} ui<j<ui+1,j+aj<ui+1.

d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示从 n n n开始考虑到第 i i i位,唯一路径后两个位置位 j j j k k k的最小代价.

两种转移:

  • i i i不在当前路径中, d p [ i ] [ j ] [ k ] ← d p [ i + 1 ] [ j ] [ k ] + [ j ≤ i + a i ] dp[i][j][k]←dp[i+1][j][k]+[j≤i+a_i] dp[i][j][k]dp[i+1][j][k]+[ji+ai],因为 j ≤ i + a i j≤i+a_i ji+ai的时候,需要把第 i i i位修改成0,代价 + 1 +1 +1.
  • i i i在当前路径中, d p [ i ] [ i ] [ j ] ← d p [ i + 1 ] [ j ] [ k ] dp[i][i][j]←dp[i+1][j][k] dp[i][i][j]dp[i+1][j][k],需要满足 j ≤ i + a i < k j≤i+a_i<k ji+ai<k.

这时候可以打出一个 O ( n 3 ) O(n^3) O(n3)暴力来:

const int MXN = 3e3 + 5;
int n, m;
int ar[MXN];
void work() {
    n = read();
    rep(i, 1, n + 1) ar[i] = read();
    vector<vector<vector<int>>> dp(n + 2, vector<vector<int>>(n + 2, vector<int>(n + 2, INF)));
    dp[n][n][n + 1] = 0;
    per(i, n - 1, 1) {
        //i不在当前路径中
        rep(j, i + 1, n + 1) {
            rep(k, j + 1, n + 2) {
                dp[i][j][k] = min(dp[i][j][k], dp[i + 1][j][k] + (i + ar[i] >= j));
            }
        }
        if(ar[i] == 0) continue;
        //i在当前路径中
        rep(j, i + 1, i + ar[i] + 1) {
            rep(k, i + ar[i] + 1, n + 2) {
                dp[i][i][j] = min(dp[i][i][j], dp[i + 1][j][k]);
            }
        }
    }
    int ans = INF;
    rep(i, 1, n) {
        rep(j, i + 1, n + 1) {
            if(1 + ar[1] >= i) ans = min(ans, dp[1][i][j]);
        }
    }
    printf("%d\n", ans);
}

考虑如何优化这两个转移:

优化一:

第二种转移,对所有 i + 1 ≤ j ≤ i + a i i+1≤j≤i+a_i i+1ji+ai i i i在当前路径中, d p [ i ] [ i ] [ j ] ← d p [ i + 1 ] [ j ] [ k ] dp[i][i][j]←dp[i+1][j][k] dp[i][i][j]dp[i+1][j][k],需要满足 j ≤ i + a i < k j≤i+a_i<k ji+ai<k

他的操作本质是 d p [ ] [ i ] [ j ] = m i n k > i + a i d p [ ] [ j ] [ k ] dp[][i][j]=min_{k>i+ai}dp[][j][k] dp[][i][j]=mink>i+aidp[][j][k].

这个东西 m i n k > i + a i d p [ ] [ j ] [ k ] min_{k>i+ai}dp[][j][k] mink>i+aidp[][j][k]维护一个后缀最小值即可解决: s u f [ ] [ j ] [ k ] = m i n ( d p [ ] [ j ] [ k ] , s u f [ ] [ j ] [ k + 1 ] ) suf[][j][k]=min(dp[][j][k],suf[][j][k+1]) suf[][j][k]=min(dp[][j][k],suf[][j][k+1])

修改一下有代码如下:

const int MXN = 3e3 + 5;
int n, m;
int ar[MXN];
void work() {
    n = read();
    rep(i, 1, n + 1) ar[i] = read();
    vector<vector<vector<int>>> dp(n + 2, vector<vector<int>>(n + 2, vector<int>(n + 2, INF))), suf(n + 2, vector<vector<int>>(n + 2, vector<int>(n + 2, INF)));
    dp[n][n][n + 1] = 0;
    suf[n][n][n + 1] = 0;
    per(i, n - 1, 1) {
        //i不在当前路径中
        rep(j, i + 1, n + 1) {
            rep(k, j + 1, n + 2) {
                dp[i][j][k] = min(dp[i][j][k], dp[i + 1][j][k] + (i + ar[i] >= j));
            }
            suf[i][j][n + 1] = dp[i][j][n + 1];
            per(k, n, j + 1) {
                suf[i][j][k] = min(suf[i][j][k + 1], dp[i][j][k]);
            }
        }
        if(ar[i] == 0) continue;
        //i在当前路径中
        rep(j, i + 1, i + ar[i] + 1) {
            dp[i][i][j] = min(dp[i][i][j], suf[i + 1][j][i + ar[i] + 1]);
        }
        suf[i][i][n + 1] = dp[i][i][n + 1];
        per(j, n, i + 1) {
            suf[i][i][j] = min(suf[i][i][j + 1], dp[i][i][j]);
        }
    }
    int ans = INF;
    rep(i, 1, n) {
        rep(j, i + 1, n + 1) {
            if(1 + ar[1] >= i) ans = min(ans, dp[1][i][j]);
        }
    }
    printf("%d\n", ans);
}

优化二:

第一种转移,对所有 i + 1 ≤ j ≤ i + a i i+1≤j≤i+a_i i+1ji+ai i i i不在当前路径中, d p [ i ] [ j ] [ k ] ← d p [ i + 1 ] [ j ] [ k ] + [ j ≤ i + a i ] dp[i][j][k]←dp[i+1][j][k]+[j≤i+a_i] dp[i][j][k]dp[i+1][j][k]+[ji+ai] j j j肯定要大于 i i i,就是要 a i > 0 a_i\gt 0 ai>0

他的操作本质其实是 f o r    e a c h    j ∈ [ i + 1 , i + a i + 1 ] : d p [ ] [ j ] [ k ] + = 1 for\;each\;j\in[i+1,i+a_i+1]:dp[][j][k]+=1 foreachj[i+1,i+ai+1]:dp[][j][k]+=1,且 k k k任意。

那其实我最后只要用一个二维矩阵维护 d p [ ] [ j ] [ k ] dp[][j][k] dp[][j][k]的后两维,每次更新的时候主要更新 i i i在唯一路径中的情况。

i i i不在唯一路径中的情况的话,其实只要用 a d d [ j ] add[j] add[j] 记录 j j j全部加 1 1 1操作的次数即可。

最终代码如下:

时间复杂度: O ( n 2 ) O(n^2) O(n2).

#include <bits/stdc++.h>
#define fi first
#define se second
#define o2(x) (x) * (x)
#define mk make_pair
#define eb emplace_back
#define SZ(x) ((int)(x).size())
#define all(x) (x).begin(), (x).end()
#define clr(a, b) memset((a), (b), sizeof((a)))
#define rep(i, s, t) for(register int i = (s), LIM=(t); i < LIM; ++i)
#define per(i, s, t) for(register int i = (s), LIM=(t); i >= LIM; --i)
#define GKD std::ios::sync_with_stdio(false);cin.tie(0)
#define my_unique(x) sort(all(x)), x.erase(unique(all(x)), x.end())
using namespace std;
typedef long long LL;
typedef long long int64;
typedef unsigned long long uint64;
typedef pair<int, int> pii;
// mt19937 rng(time(NULL));//std::clock()
// mt19937_64 rng64(chrono::steady_clock::now().time_since_epoch().count());
// shuffle(arr, arr + n, rng64);
inline int64 read() {
    int64 x = 0;int f = 0;char ch = getchar();
    while (ch < '0' || ch > '9') f |= (ch == '-'), ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch =
    getchar(); return x = f ? -x : x;
}
inline void write(int64 x, bool f = true) {
    if (x == 0) {putchar('0'); if(f)putchar('\n');else putchar(' ');return;}
    if (x < 0) {putchar('-');x = -x;}
    static char s[23];
    int l = 0;
    while (x != 0)s[l++] = x % 10 + 48, x /= 10;
    while (l)putchar(s[--l]);
    if(f)putchar('\n');else putchar(' ');
}
int lowbit(int x) { return x & (-x); }
template <class T>
T big(const T &a1, const T &a2) {return a1 > a2 ? a1 : a2;}
template <class T>
T sml(const T &a1, const T &a2) {return a1 < a2 ? a1 : a2;}
template <typename T, typename... R>
T big(const T &f, const R &... r) {return big(f, big(r...));}
template <typename T, typename... R>
T sml(const T &f, const R &... r) {return sml(f, sml(r...));}
void debug_out() { cout << '\n'; }
template <typename T, typename... R>
void debug_out(const T &f, const R &... r) {
    cout << f << " ";
    debug_out(r...);
}
#ifdef LH_LOCAL
#define debug(...) cout << "[" << #__VA_ARGS__ << "]: ", debug_out(__VA_ARGS__);
#else
#define debug(...) ;
#endif
/*================Header Template==============*/
const int mod = 998244353;// 998244353
int ksm(int a, int64 b, int kmod = mod) {int res = 1;for(;b > 0;b >>= 1, a = (int64)a * a % kmod) if(b &1) res = (int64)res * a % kmod;return res;}
const int INF = 0x3f3f3f3f;
const int MXN = 3e3 + 5;
int n, m;
int ar[MXN];
void work() {
    n = read();
    rep(i, 1, n + 1) ar[i] = read();
    vector<vector<int>> dp(n + 2, vector<int>(n + 2, INF)), suf(n + 2, vector<int>(n + 2, INF));
    vector<int> add(n + 2, 0);
    dp[n][n + 1] = 0;
    suf[n][n + 1] = 0;
    per(i, n - 1, 1) {
        rep(j, i + 1, n + 1) {
            if(j <= i + ar[i]) dp[i][j] = min(dp[i][j], suf[j][i + ar[i] + 1] + add[j]);
        }
        per(j, n, i) suf[i][j] = min(suf[i][j + 1], dp[i][j]);
        rep(j, i + 1, i + ar[i] + 1) ++ add[j];
    }
    printf("%d\n", suf[1][2]);
}

int main() {
#ifdef LH_LOCAL
    freopen("D:/ACM/mtxt/in.txt", "r", stdin);
    // freopen("D:/ACM/mtxt/out.txt", "w", stdout);
#endif
    for(int cas = 1, tim = read(); cas <= tim; ++ cas) {
        // printf("Case #%d:\n", cas);
        work();
    }
#ifdef LH_LOCAL
    cout << "time cost:" << 1.0 * clock() / CLOCKS_PER_SEC << "s" << endl;
#endif
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值