一些干净的DP题目

EASY

Making the Grade

给一个长为n(n<2000)的序列A,将其变成一个非增或非减的序列B的绝对值改变量。
在这里插入图片描述
key:B中所有的数都是A中的数。因为若 a l < b < a r a_l<b<a_r al<b<ar时,总能令b变成其中一个值。则枚举 b i b_i bi为每个A中的值即可。dp[i][j]表示 b i ≤ u n q j b_i≤unq_j biunqj时,目前的最小代价。

// #include<bits/stdc++.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 2e3+4;
int n,m;
int a[N],unq[N];
int dp[N][N];
int solve(){
    for(int i=0;i<m;++i){
        dp[0][i]=abs(a[0]-unq[i]);
        if(i)dp[0][i]=min(dp[0][i-1],dp[0][i]);
    }
    for(int i=1;i<n;++i){
        for(int j=0;j<m;++j){
            dp[i][j]=dp[i-1][j]+abs(a[i]-unq[j]);
            if(j)dp[i][j]=min(dp[i][j-1],dp[i][j]);
        }
    }
    return dp[n-1][m-1];
}
int main(){
    cin>>n;
    for(int i=0;i<n;++i){
        cin>>a[i];
        unq[i]=a[i];
    }
    sort(unq,unq+n);
    m=unique(unq,unq+n)-unq;
    int ans=solve();
    reverse(a,a+n);
    ans=min(ans,solve());
    cout<<ans<<endl;
}

Milking Time

题意:给出 m m m个时间区间,完成每个区间的事可以有一定的贡献。每做完一件事要缓 r r r分钟。问最多有多少贡献。
思路:由于时间范围为1e6。我和大部分题解双重循环区间不同。我遍历一遍时间。dp[i]表示前i分钟做的最大贡献。缓r分钟解决方式是每个区间的结束时间 + r +r +r。天坑:一个区间的ending_time可以开始另一件事了。

// #include<bits/stdc++.h>
#include<iostream>
#include<algorithm>
#include<unordered_map>
using namespace std;
typedef long long ll;
const int N = 1e6+4,M=1e3+5;
struct Node{
    int l,v,nex;
}e[M];
int n,m,r;
ll dp[N+N];
unordered_map<int,int> head;
void add(int a,int b,int c){
    static int cnt=0;
    e[++cnt] = Node{a,c,head.count(b) ? head[b] : 0};
    head[b] = cnt;
}
int main(){
    cin>>n>>m>>r;
    for(int a,b,c,i=1;i<=m;++i){
        cin>>a>>b>>c;
        if(b<=n)add(a,b+r,c);
    }
    for(int i=1;i<=n+r;++i){
        dp[i] = dp[i-1];
        if(head.count(i))for(int j=head[i];j;j=e[j].nex){
            dp[i]=max(dp[e[j].l]+e[j].v,dp[i]);
        }
    }
    cout<<dp[n+r]<<endl;
}

Phalanx

给一个 n × n n×n n×n(n<1000)字符矩阵。求沿对角线丿对称的最大子矩阵。dp[siz][i][j]表示右下角坐标为(i,j),大小为siz+1的矩阵是否是对称矩阵。

// #include<bits/stdc++.h>
#include<iostream>
#include<algorithm>
#include<unordered_map>
using namespace std;
#define debug(_x) cout<<#_x<<": "<<_x<<endl;
typedef long long ll;
const int N = 1e6+4,M=1e3+5;
char a[M][M];
bool dp[2][M][M];
bool (*f1)[M],(*f2)[M];
int n;
bool solve(int siz){
    bool ans=0;
    for(int i=siz;i<n;++i)
    for(int j=siz;j<n;++j){
        ans |= f1[i][j] = (f2[i-1][j-1] && f2[i][j] && a[i-siz][j]==a[i][j-siz]);
    }
    return ans;
}
int main(){
    while(cin>>n&&n){
        for(int i=0;i<n;++i){
            cin>>a[i];
            reverse(a[i],a[i]+n);
        }
        for(int i=0;i<n;++i)
            for(int j=0;j<n;++j)
                dp[0][i][j]=1;
        f2=dp[0];
        f1=dp[1];
        int ans=0;
        for(int i=1;i<n;++i,swap(f1,f2))
            if(solve(i))ans=i;
        cout<<ans+1<<endl;
    }
}

CF1114D. Flood Fill

题意:给 n ( n ≤ 5000 ) n(n\le 5000) n(n5000)个数,选择一个起始点,可以让颜色相同的整段变色。问最少的变色次数,让所有颜色相同。
思路:假设选好了起始点 i i i,则如何求变色次数?显然答案是 n − 1 − f [ i − 1 ] [ i + 1 ] n-1-f[i-1][i+1] n1f[i1][i+1],其中 f [ i − 1 ] [ i + 1 ] f[i-1][i+1] f[i1][i+1]代表 c 1 ⋯ c i − 1 c_1\cdots c_{i-1} c1ci1 c n ⋯ c i + 1 c_n\cdots c_{i+1} cnci+1的最长公共子序列。则直接求原数组和逆序数组的最长公共子序列即可。 O ( n 2 ) O(n^2) O(n2)

#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);
using namespace std;
const int N = 5005;
int f[N][N], c[N];
int main(){
    IOS;
    int n;
    cin >> n;
    for(int i = 1; i <= n; ++i){
        cin >> c[i];
    }
    n = unique(c + 1, c + n + 1) - c - 1;
    for(int i = 1; i <= n; ++i){
        for(int j = n; j > 0; --j){
            f[i][j] = max({f[i-1][j], f[i][j+1], f[i-1][j+1] + (c[i] == c[j])});
        }
    }
    int ans = 0;
    for(int i = 1; i <= n; ++i)
        ans = max(ans, f[i-1][i+1]);
    cout << n - 1 - ans << endl;
}

MID

CF1458B. Glass Half Spilled

题意:有 n ( n ≤ 100 ) n(n\le 100) n(n100)个玻璃杯,每个玻璃杯的容量是 a i a_i ai,已有水量是 b i b_i bi,倒水有一半的损失。挑 k k k个杯子装水,求最大的装水量。
第一个思路:dp[i][k][j]表示 1... i 1...i 1...i个杯子,挑 k k k个,剩余容量为j的最大装水量。但是这个状态转移具有后效性,可能前面后面要的杯子,用前面的杯子里的水。
题解dp[i][k][j]表示 1... i 1...i 1...i个杯子,挑 k k k个,总容量为 j j j的最大水量(未考虑是否装的下,只考虑有多少水)。状态转移如下。则取 k k k杯的答案就是max(j, dp[i][k][j])。时间复杂度 O ( n 3 ⋅ max ⁡ a ) O(n^3\cdot \max a) O(n3maxa),达到1e8。

dp[i][k][j]=max(dp[i-1][k][j] + b[i]/2, dp[i-1][k-1][j-a[i]] + b[i]);
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);
using namespace std;
using ll = long long;
const int N = 1e2 + 5;
int dp[N][N][10005];
int a[N], b[N], ans[N];
int main(){
    IOS;
    int n, CAP = 0;
    cin >> n;
    for(int i = 1; i <= n; ++i)
        cin >> a[i] >> b[i];
    memset(dp, 0xf0, sizeof(dp));
    dp[0][0][0] = 0;
    for(int i = 1; i <= n; ++i){
        CAP += a[i];
        for(int k = 0; k <= n; ++k){
            for(int j = 0; j <= CAP; ++j){
                int &now = dp[i][k][j];
                now = dp[i-1][k][j] + b[i];
                if(k && j - a[i] >= 0)now = max(now, dp[i-1][k-1][j-a[i]] + 2 * b[i]);
                if(ans[k] < min(now, 2 * j))ans[k] = min(now, 2 * j);
            }
        }
    }
    for(int k = 1; k <= n; ++k)
        cout << fixed << setprecision(6) << ans[k] / 2.0 << " ";
    cout << endl;
}

CF1433F. Zero Remainder Sum

题意:给定 n × m n×m n×m矩阵,每行选最多 m / 2 m/2 m/2个。求能被 m o d mod mod整除的最大和。(所有数小于70)
思路:对于第 i i i行来说, f [ j ] [ h ] [ k ] f[j][h][k] f[j][h][k]表示 1... j 1...j 1...j h h h个,对 m o d mod mod取余为 k k k的最大和。易得转移公式
f [ j ] [ h ] [ k ] = max ⁡ ( f [ j − 1 ] [ h ] [ k ] ,          f [ j − 1 ] [ h − 1 ] [ ( m o d + k − a [ i ] [ j ] % m o d ) % m o d ] + a [ i ] [ j ] ) f[j][h][k]=\max(f[j-1][h][k],\\ \ \ \ \ \ \ \ \ f[j-1][h-1][(mod+k-a[i][j]\%mod)\%mod]+a[i][j]) f[j][h][k]=max(f[j1][h][k],        f[j1][h1][(mod+ka[i][j]%mod)%mod]+a[i][j])
最后, s u m [ i ] [ k ] = max ⁡ j , h f [ j ] [ h ] [ k ] sum[i][k]=\max_{j,h}{f[j][h][k]} sum[i][k]=maxj,hf[j][h][k],再对行进行dp
d p [ i ] [ k ] = max ⁡ 0 ≤ l a < m o d ( , d p [ i − 1 ] [ l a ] + s u m [ i ] [ ( m o d + k − l a ) dp[i][k] = \max_{0\le la<mod}(, dp[i-1][la] + sum[i][(mod+k-la)%mod]) dp[i][k]=0la<modmax(,dp[i1][la]+sum[i][(mod+kla)
最终答案就是 d p [ n ] [ 0 ] dp[n][0] dp[n][0],时间复杂度为 O ( n ⋅ m 2 ⋅ m o d ) O(n\cdot m^2 \cdot mod) O(nm2mod)

#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);
using namespace std;
using ll = long long;
const int N = 73;
int f[N][N/2][N], sum[N][N], dp[N][N];
int main(){
    IOS;
    int n, m, mod;
    cin >> n >> m >> mod;
    memset(dp, 0xf0, sizeof(dp));
    memset(sum, 0xf0, sizeof(sum));
    dp[0][0] = 0;
    for(int now, i = 1; i <= n; ++i){
        memset(f[0], 0xf0, sizeof(f[0]));
        f[0][0][0] = 0;
        sum[i][0] = 0;
        for(int j = 1; j <= m; ++j){
            cin >> now;
            for(int h = 0; h <= m / 2; ++h){
                for(int k = 0; k < mod; ++k){
                    f[j][h][k] = f[j-1][h][k];
                    if(h && f[j][h][k] < f[j-1][h-1][(mod+k-now%mod) % mod] + now)
                        f[j][h][k] = f[j-1][h-1][(mod+k-now%mod) % mod] + now;
                    if(sum[i][k] < f[j][h][k])
                        sum[i][k] = f[j][h][k];
                }
            }
        }
        for(int k = 0; k < mod; ++k){
            for(int la = 0; la < mod; ++la){
                dp[i][k] = max(dp[i][k], dp[i-1][la] + sum[i][(mod+k-la)%mod]);
            }
        }
    }
    cout << dp[n][0] << endl;
}

CF1312E. Array Shrinking

题意:给 n ( n ≤ 500 ) n(n\le 500) n(n500)个数,若相邻两个数相同( a i = = a i + 1 a_i==a_{i+1} ai==ai+1)则可以用 a i + 1 a_i+1 ai+1代替。问最后合并出最少的项数。
思路

  • 先考虑只有相邻的多个数合并成一个数的所有情况。当可以合并成一个数时,这个数必然是唯一的。即 n u m [ i ] [ j ] num[i][j] num[i][j]表示把 a i ⋯ a j a_i\cdots a_j aiaj合并成一个数,那个数的值,如果不能的话, n u m [ i ] [ j ] = 0 num[i][j]=0 num[i][j]=0.就是一个简单的区间DP
  • 再考虑最少用多少个数即 n u m num num区间来得到整个 1 ⋯ n 1\cdots n 1n区间。设 f [ i ] f[i] f[i]表示 1 ⋯ i 1\cdots i 1i区间最少有多少个数。
    f [ i ] = min ⁡ 0 ≤ j < i f [ j ] + 1      ( i f   n u m [ j + 1 ] [ i ] ≠ 0 ) f[i] = \min_{0\le j<i}{f[j] + 1}\ \ \ \ (if\ num[j+1][i]\not=0) f[i]=0j<iminf[j]+1    (if num[j+1][i]=0)
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);
using namespace std;
using ll = long long;
const int N = 505;
int num[N][N], f[N];
int main(){
    IOS;
    int n;
    cin >> n;
    for(int i = 1; i <= n; ++i)
        cin >> num[i][i];
    for(int len = 2; len <= n; ++len){
        for(int i = 1, j = i + len - 1; j <= n; ++i, ++j){
            for(int k = i; k < j; ++k){
                if(num[i][k] == num[k+1][j] && num[i][k])
                    num[i][j] = num[i][k] + 1;
            }
        }
    }
    f[0] = 0;
    for(int i = 1; i <= n; ++i){
        f[i] = i;
        for(int j = 0; j < i; ++j){
            if(num[j+1][i])f[i] = min(f[i], f[j] + 1);
        }
    }
    cout << f[n] << endl;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值