一些比较杂的动态规划

本文探讨了单调队列优化在解决数论和算法问题中的作用,特别是在音乐家的谱面问题中,通过预处理和区间处理实现了高效解决方案。同时,介绍了如何利用性质优化将复杂度降低,如在处理最大子数组和问题时,通过动态规划和分治策略实现快速计数。文章还展示了如何在简单背包计数问题中,通过优化避免超时,从而达到较高的效率。
摘要由CSDN通过智能技术生成

音游家的谱面(Hard version)

一道稍微有点难度的单调队列+性质优化题。有两种情况分类讨论用不同的优化方式,强烈建议写一下

#include <bits/stdc++.h>

#define ll long long
#define int long long
#define pii pair<int, int>
#define rep(i, x, y) for(int i = x; i <= y; ++i)
#define dep(i, x, y) for(int i = x; i >= y; --i)
#define debug(x) cout << #x": " << x << endl;
#define ct cout << endl;
using namespace std;
const int maxn = 5e3+10;
const ll inf = 1e18;

template <typename _tp>
inline void read(_tp& x) {
    char ch = getchar(), sgn = 0;
    while (ch ^ '-' && !isdigit(ch)) ch = getchar();
    if (ch == '-') ch = getchar(), sgn = 1;
    for (x = 0; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
    if (sgn) x = -x;
}
int n, m, pos[maxn];
int f[maxn][maxn], pre[maxn][maxn];
pii mn[maxn];
pii que[maxn];
int res[maxn];
int L[maxn], R[maxn];
pii checkmin(pii a, pii b){
    if(a.first < b.first) return a;
    return b;
}

void dfs(int cur, int x){
    res[cur] = f[cur][x];
    if(cur == 1) return;
    dfs(cur-1, pre[cur][x]);
}
signed main(){
    read(n), read(m);
    rep(i, 1, m) read(pos[i]);
    rep(i, 1, n){
        f[1][i] = min(max(pos[1]-1, n-i), max(i-1, n-pos[1]));
        //debug(f[1][i]);
    }
    mn[0] = make_pair(inf, -1);
    mn[n+1] = make_pair(inf, -1);
    rep(i, 2, m){
        int tmp = abs(pos[i] - pos[i-1]);
        int head = 1, tail = 0;
        int len = 1;
        rep(j, 1, n) f[i][j] = inf;
        rep(j, 1, n){
            int l = max(j - tmp, 1ll), r = min(j + tmp, n);
            while(len <= n && len <= r){
                while(tail - head + 1 > 0 && que[tail].first >= f[i-1][len] + tmp) tail--;
                que[++tail] = make_pair(f[i-1][len] + tmp, len);
                ++len;
            }
            while(tail - head + 1 > 0 && que[head].second < l) head++;
            if(tail - head + 1 > 0){
                if(f[i][j] > que[head].first){
                    f[i][j] = que[head].first;
                    pre[i][j] = que[head].second;
                }
            }
        }
        rep(j, 1, n) mn[j] = make_pair(inf, -1);
        rep(j, 1, n){
            int tp = abs(pos[i] - j);
            int l = max(pos[i-1] - tp, 1ll), r = min(pos[i-1] + tp, n);
            mn[l] = min(make_pair(f[i-1][j] + tp, j), mn[l]);
            mn[r] = min(make_pair(f[i-1][j] + tp, j), mn[r]);
        }
        rep(j, 1, pos[i-1]) mn[j] = min(mn[j], mn[j-1]);
        dep(j, n, pos[i-1]) mn[j] = min(mn[j], mn[j+1]);
        rep(j, 1, n){
            if(f[i][j] > mn[j].first){
                f[i][j] = mn[j].first;
                pre[i][j] = mn[j].second;
            }
            //debug(f[i][j]);
        }
        //ct
    }
    int ans = inf, index;
    rep(i, 1, n) {
        //debug(f[m][i]);
        if(ans > f[m][i]){
            ans = f[m][i];
            index = i;
        }
    }
    dfs(m, index);
    rep(i, 1, m) cout << res[i] << " ";
    return 0;
}

音乐家的曲调

对于每个点 j j j 用双指针预处理出满足条件的情况下能到最左边的下标 L [ j ] L[j] L[j]
对于每个区间单独处理,处理分第 i i i 个区间,用分 i − 1 i-1 i1 个区间的状态进行转移。
f [ i ] [ j ] = m a x ( f [ i ] [ j − 1 ] , f [ i − 1 ] [ L [ j ] − 1 ] + j − L [ j ] + 1 ) f[i][j] =max(f[i][j-1], f[i-1][L[j]-1]+j-L[j]+1) f[i][j]=max(f[i][j1],f[i1][L[j]1]+jL[j]+1)
f [ i ] [ j ] f[i][j] f[i][j] 表示分前 i i i 个区间字符串前 j j j 个最长长度
很显然,其实从 k ∈ [ L [ j ] ,   j − 1 ] k\in[L[j], \ j-1] k[L[j], j1] 都可以转移到 j j j 这个状态
假设, k ′ ∈ [ L [ j ] ,   j − 1 ] k^{\prime}\in[L[j], \ j-1] k[L[j], j1], 此时 f [ i ] [ j ] = f [ i − 1 ] [ k ′ − 1 ] + j − k ′ + 1 f[i][j] = f[i-1][k^{\prime}-1]+j-k^{\prime}+1 f[i][j]=f[i1][k1]+jk+1
假设 L [ L [ j − 1 ] ] = = 1 L[L[j-1]] == 1 L[L[j1]]==1 那么此时 m a x ( f [ i ] [ j ] ) = = j max(f[i][j])==j max(f[i][j])==j
此时, L [ k ′ ] < L [ j ]     f [ i − 1 ] [ L [ k ′ ] − 1 ] = L [ k ′ ] − 1 L[k^{\prime}]< L[j] \ \ \ f[i-1][L[k^{\prime}]-1] = L[k^{\prime}]-1 L[k]<L[j]   f[i1][L[k]1]=L[k]1
所以 f [ i − 1 ] [ k ′ − 1 ] + j − k ′ + 1 ≤ f [ i − 1 ] [ L [ j ] − 1 ] + j − L [ j ] + 1 f[i-1][k^{\prime}-1]+j-k^{\prime}+1 \leq f[i-1][L[j]-1]+j-L[j]+1 f[i1][k1]+jk+1f[i1][L[j]1]+jL[j]+1

#include <bits/stdc++.h>

#define ll long long
#define int long long
#define pii pair<int, int>
#define rep(i, x, y) for(int i = x; i <= y; ++i)
#define dep(i, x, y) for(int i = x; i >= y; --i)
#define debug(x) cout << #x": " << x << endl;
#define ct cout << endl;
using namespace std;
const int maxn = 1e7+10;
const ll inf = 1e18;

template <typename _tp>
inline void read(_tp& x) {
    char ch = getchar(), sgn = 0;
    while (ch ^ '-' && !isdigit(ch)) ch = getchar();
    if (ch == '-') ch = getchar(), sgn = 1;
    for (x = 0; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
    if (sgn) x = -x;
}

char s[maxn];
int f[2][maxn];
int cnt[30];
int L[maxn];
signed main(){
    int n, m;
    read(n), read(m);
    scanf("%s", s+1);
    int t = 0;
    int l = 1;
    rep(i, 1, n){
        int c = s[i] - 'a';
        cnt[c]++;
        while(cnt[c] > m){
            cnt[s[l] - 'a']--;
            ++l;
        }
        L[i] = l;
    }
    rep(i, 1, 3){
        t ^= 1;
        rep(j, 1, n){
            f[t][j] = max(f[t][j-1], f[t^1][L[j]-1] + j - L[j] + 1);
        }
    }
    cout << f[t][n];
    return 0;
}

AtCoder Beginner Contest 207 E - Mod i

f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i 个数分 j j j 组的情况数
很显然能得出这个式子, f [ i ] [ j ] + = f [ k ] [ j − 1 ] × ( ( ∑ l = k + 1 i a [ l ] )   m o d   j = = 0 ) f[i][j] \mathrel{+}= f[k][j-1]\times ((\displaystyle\sum^{i}_{l=k+1}{a[l}])\ mod\ j==0) f[i][j]+=f[k][j1]×((l=k+1ia[l]) mod j==0)
取一个前缀和就变成了 f [ i ] [ j ] + = f [ k ] [ j − 1 ] × ( ( s u m [ i ] − s u m [ k ] )   m o d   j = = 0 ) f[i][j] \mathrel{+}= f[k][j-1]\times ((sum[i]-sum[k])\ mod\ j==0) f[i][j]+=f[k][j1]×((sum[i]sum[k]) mod j==0) 很明显这个式子的复杂度是 O ( n 3 ) O(n^3) O(n3)
我们可以发现对于 ( s u m [ i ] − s u m [ k ] )   m o d   j = = 0 (sum[i]-sum[k])\ mod\ j==0 (sum[i]sum[k]) mod j==0 可以转换为 s u m [ i ]   m o d   j = = s u m [ k ]   m o d   j sum[i] \ mod\ j==sum[k] \ mod\ j sum[i] mod j==sum[k] mod j 也就是说,对于 k ∈ [ 1 ,   i − 1 ] k\in[1,\ i-1] k[1, i1],只要满足 s u m [ i ]   m o d   j = = s u m [ k ]   m o d   j sum[i] \ mod\ j==sum[k] \ mod\ j sum[i] mod j==sum[k] mod j 就是满足条件的
问题就转换为对于当前的 a [ i ] a[i] a[i] 要分 j j j 组只要找到满足条件的 k k k 使 s u m [ i ]   m o d   j = = s u m [ k ]   m o d   j sum[i] \ mod\ j==sum[k] \ mod\ j sum[i] mod j==sum[k] mod j,这样我们只要维护一个 f [ j ] [ s u m [ i ]   m o d   ( j + 1 ) ] f[j][sum[i]\ mod\ (j+1)] f[j][sum[i] mod (j+1)] 根据上面的定义,转移就变成了 f [ j ] [ s u m [ i ]   m o d   ( j + 1 ) ] = f [ j ] [ s u m [ i ]   m o d   j ] f[j][sum[i]\ mod\ (j+1)] = f[j][sum[i]\ mod\ j] f[j][sum[i] mod (j+1)]=f[j][sum[i] mod j]
有一个小细节, d p dp dp 数组维护的不是答案,所以答案统计的的时候只要求最后一次转移的总和就行了

#include <bits/stdc++.h>

#define ll long long
#define int long long
#define pii pair<int, int>
#define rep(i, x, y) for(int i = x; i <= y; ++i)
#define dep(i, x, y) for(int i = x; i >= y; --i)
#define debug(x) cout << #x": " << x << endl;
#define ct cout << endl;
#define mod 1000000007
using namespace std;
const int maxn = 3010;
const ll inf = 1e18;

template <typename _tp>
inline void read(_tp& x) {
    char ch = getchar(), sgn = 0;
    while (ch ^ '-' && !isdigit(ch)) ch = getchar();
    if (ch == '-') ch = getchar(), sgn = 1;
    for (x = 0; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
    if (sgn) x = -x;
}

int f[maxn][maxn], a[maxn], sum[maxn];
signed main(){
    //freopen("1.in", "r", stdin);
    //freopen("1.out", "w", stdout);
    int n;
    read(n);
    rep(i, 1, n) read(a[i]);
    rep(i, 1, n) sum[i] = (sum[i-1] + a[i]);
    int ans = 0;
    f[0][0] = 1;
    rep(i, 1, n){
        dep(j, n, 1){
            f[j][sum[i]%(j+1)] = (f[j][sum[i]%(j+1)] + f[j-1][sum[i]%j]) % mod; 
            if(i == n) ans += f[j-1][sum[i]%j], ans %= mod;
        }
    }
    cout << ans << endl;
    return 0;
}

Max Sum Counting
一道简单背包计数
O ( 500 0 3 ) O(5000^3) O(50003) 的做法很好出,很显然,你确定完 m a x ( a [ i ] ) max(a[i]) max(a[i]) 之后,你就不能取 a [ j ] > a [ i ] a[j] > a[i] a[j]>a[i] 对应的 b [ j ] b[j] b[j] 了,那么对于 a [ i ] a[i] a[i] 从大到小排个序,背包计数就行了

#include <bits/stdc++.h>

#define ll long long
#define int long long
#define pii pair<int, int>
#define rep(i, x, y) for(int i = x; i <= y; ++i)
#define dep(i, x, y) for(int i = x; i >= y; --i)
#define debug(x) cout << #x": " << x << endl;
#define ct cout << endl;
#define mod 998244353
using namespace std;
const int maxn = 1e6+10;
const ll inf = 1e18;

template <typename _tp>
inline void read(_tp& x) {
    char ch = getchar(), sgn = 0;
    while (ch ^ '-' && !isdigit(ch)) ch = getchar();
    if (ch == '-') ch = getchar(), sgn = 1;
    for (x = 0; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
    if (sgn) x = -x;
}
pii a[maxn];

bool cmp(pii x, pii y){
    return x.first > y.first;
}
int f[5001];
signed main(){
    //freopen("1.in", "r", stdin);
    //freopen("1.out", "w", stdout);
    int n;
    read(n);
    rep(i, 1, n) read(a[i].first);
    rep(i, 1, n) read(a[i].second);
    sort(a + 1, a + 1 + n, cmp);
    int ans = 0;
    rep(i, 1, n){
        if(a[i].first < a[i].second) continue;
        int tmp = a[i].first - a[i].second;
        ans = (ans + 1) % mod;
        memset(f, 0, sizeof(f));
        f[0] = 1;
        rep(j, i+1, n){
            dep(k, tmp, a[j].second){
                f[k] += f[k - a[j].second];
                f[k] %= mod;
            }
        }
        rep(j, 1, tmp) ans = (ans + f[j]) % mod;
    }
    cout << ans;
    return 0;
}

很显然会被T飞,那么考虑优化,对于每个 a [ i ] a[i] a[i] 来说,我要对 i + 1 − n i+1 - n i+1n 重新跑一遍背包计数,这很费时间,如果从右往左推的话,把每个 b [ i ] b[i] b[i] 加入背包计数里面,只要跑一遍背包即可,复杂度 O ( 500 0 2 ) O(5000^2) O(50002)

#include <bits/stdc++.h>

#define ll long long
#define int long long
#define pii pair<int, int>
#define rep(i, x, y) for(int i = x; i <= y; ++i)
#define dep(i, x, y) for(int i = x; i >= y; --i)
#define debug(x) cout << #x": " << x << endl;
#define ct cout << endl;
#define mod 998244353
using namespace std;
const int maxn = 1e6+10;
const ll inf = 1e18;

template <typename _tp>
inline void read(_tp& x) {
    char ch = getchar(), sgn = 0;
    while (ch ^ '-' && !isdigit(ch)) ch = getchar();
    if (ch == '-') ch = getchar(), sgn = 1;
    for (x = 0; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
    if (sgn) x = -x;
}
pii a[maxn];

bool cmp(pii x, pii y){
    return x.first > y.first;
}
int f[5010];
signed main(){
    //freopen("1.in", "r", stdin);
    //freopen("1.out", "w", stdout);
    int n;
    read(n);
    rep(i, 1, n) read(a[i].first);
    rep(i, 1, n) read(a[i].second);
    sort(a + 1, a + 1 + n, cmp);
    int ans = 0;
    f[0] = 1;
    dep(i, n, 1){
        if(a[i].first >= a[i].second) ans = (ans + 1) % mod;
        int tmp = a[i].first - a[i].second;
        if(tmp >= 1) rep(j, 1, tmp) ans = (ans + f[j]) % mod;
        dep(j, 5000, a[i].second) f[j] = (f[j] + f[j - a[i].second]) % mod;
    }
    // rep(i, 1, n){
    //     if(a[i].first < a[i].second) continue;
    //     int tmp = a[i].first - a[i].second;
    //     ans = (ans + 1) % mod;
    //     memset(f, 0, sizeof(f));
    //     f[0] = 1;
    //     rep(j, i+1, n){
    //         dep(k, tmp, a[j].second){
    //             f[k] += f[k - a[j].second];
    //             f[k] %= mod;
    //         }
    //     }
    //     rep(j, 1, tmp) ans = (ans + f[j]) % mod;
    // }
    cout << ans;
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值