codeforce#dp专项

Posted iuk11

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了codeforce#dp专项相关的知识,希望对你有一定的参考价值。

写在之前,题单来自jackyan

1 https://codeforces.com/problemset/problem/467/C

【题意】给定一个长度为n的序列,找到k个长度为m的子串(不是子序列),求能得到的每个子串相加后的最大值。特别,子串下标不能重复且不能交叉
i i i 表示下标
j j j 表示子串个数
不新增子串,那最大值与上一次的状态相同;
新增子串,那最大值为以当前下标 i i i 结尾的 [ i − m + 1 , i ] [i-m+1,i] [im+1,i] 长度的子串,自然 d p [ i − m ] [ j − 1 ] dp[i-m][j-1] dp[im][j1]为不增加这个子串的最大值,然后再用前缀和加上该区间的值。
两种情况比较取最大(注意第二种情况 i i i 的取值范围)
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − m ] [ j − 1 ] + b [ i ] − b [ i − m ] dp[i][j]=max(dp[i-1][j],dp[i-m][j-1]+b[i]-b[i-m] dp[i][j]=max(dp[i1][j],dp[im][j1]+b[i]b[im]

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5050;
ll a[N],b[N];
ll dp[N][N];
int main()
    int n,m,k;
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++)
        cin>>a[i];
        b[i]=b[i-1]+a[i];
    
    int cnt=0;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=k;j++)
            if(i-m>=0)
                dp[i][j]=max(dp[i-1][j],dp[i-m][j-1]+b[i]-b[i-m]);
            
            else dp[i][j]=dp[i-1][j];
        
    
    cout<<dp[n][k]<<endl;
    return 0;

2 http://codeforces.com/problemset/problem/706/C

【题意】给定 n n n 个字符串,若第 i i i 个字符串翻转则需要加上权值 w i w_i wi,若从第一个字符串开始,保证当前字典序一定不比下一个大,问满足此条件,权值和最小是多少。不满足输出-1.
首先想了个离散化,离散化了字符串,错在了前后可能有相同串出现。
因为每个状态只有翻转和不翻转两种情况,翻转权值加 w i w_i wi,则可以得到状态转移方程:
d p [ i ] [ 0 ] = m i n ( d p [ i ] [ 0 ] , d p [ i − 1 ] [ 0 ] ) s [ i ] ≥ s [ i − 1 ] d p [ i ] [ 0 ] = m i n ( d p [ i ] [ 0 ] , d p [ i − 1 ] [ 0 ] ) s [ i ] ≥ r s [ i − 1 ] d p [ i ] [ 0 ] = m i n ( d p [ i ] [ 0 ] , d p [ i − 1 ] [ 0 ] ) r s [ i ] ≥ s [ i − 1 ] d p [ i ] [ 0 ] = m i n ( d p [ i ] [ 0 ] , d p [ i − 1 ] [ 0 ] ) r s [ i ] ≥ r s [ i − 1 ] \\left\\ \\beginarrayrcl dp[i][0]=min(dp[i][0],dp[i-1][0]) & & s[i]\\geq s[i-1]\\\\ dp[i][0]=min(dp[i][0],dp[i-1][0]) & & s[i]\\geq rs[i-1]\\\\ dp[i][0]=min(dp[i][0],dp[i-1][0]) & & rs[i]\\geq s[i-1]\\\\ dp[i][0]=min(dp[i][0],dp[i-1][0]) & & rs[i]\\geq rs[i-1] \\endarray \\right. dp[i][0]=min(dp[i][0],dp[i1][0])dp[i][0]=min(dp[i][0],dp[i1][0])dp[i][0]=min(dp[i][0],dp[i1][0])dp[i][0]=min(dp[i][0],dp[i1][0])s[i]s[i1]s[i]rs[i1]rs[i]s[i1]rs[i]rs[i1]
其中 r s [ i ] rs[i] rs[i] 表示第 i i i 个字符串翻转之后的字符串。0代表不翻转,1代表翻转。
在定义正无穷时要定到1e15的级别,这道题的数据有很大的答案。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+100;
typedef long long ll;
const ll INF=1e15;
ll w[N];
string s[N],rs[N];
ll dp[N][2];
string change(string s)
    reverse(s.begin(),s.end());
    return s;

int main()
    int n;
    cin>>n;
    for(int i=1;i<=n;i++) cin>>w[i];
    for(int i=1;i<=n;i++)
        cin>>s[i];
        rs[i]=change(s[i]);
    
    // memset(dp,INF,sizeof(dp));
    for(int i=1;i<=n;i++) dp[i][0]=INF,dp[i][1]=INF;
    dp[1][0]=0;
    dp[1][1]=w[1];
    for(int i=2;i<=n;i++)
        if(s[i]>=s[i-1]) dp[i][0]=min(dp[i][0],dp[i-1][0]);
        if(s[i]>=rs[i-1]) dp[i][0]=min(dp[i][0],dp[i-1][1]);
        if(rs[i]>=s[i-1]) dp[i][1]=min(dp[i][1],dp[i-1][0]+w[i]);
        if(rs[i]>=rs[i-1]) dp[i][1]=min(dp[i][1],dp[i-1][1]+w[i]);
    
    ll ans=min(dp[n][0],dp[n][1]);
    if(ans==INF) cout<<-1<<endl;
    else cout<<ans<<endl;
    return 0;

3 https://codeforces.com/problemset/problem/611/C

【题意】有一个n*m的地图,地图上有“.”和“#”,当相邻的两块都为“.”时可以放置一个多米诺骨牌,现给定一个矩形区域,问在这个矩形区域内有多少种放置骨牌的方法。
如果直接做二维,碰到#时会交代不清楚,没法很好的转移状态,所以分为行dp和列dp进行转移,这样试一下样例发现可以很好的表示,如列dp,在一行上,如果第 i i i 个为".“和第 i − 1 i-1 i1 个为”.",可以直接写成 d p [ i ] = d p [ i − 1 ] + 1 dp[i]=dp[i-1]+1 dp[i]=dp[i1]+1 ;如果第 i i i 个为"#“或第 i − 1 i-1 i1 个为”#",可以直接写成 d p [ i ] = d p [ i − 1 ] dp[i]=dp[i-1] dp[i]=dp[i1],表示没有新增的放置方式。
d p [ i ] dp[i] dp[i] 表示 1   i 1~i 1 i 最多有多少种放置方式。

#include<bits/stdc++.h>
using namespace std;
const int N=505;
typedef long long ll;
ll hdp[N][N]以上是关于codeforce#dp专项的主要内容,如果未能解决你的问题,请参考以下文章

洛谷P1282 多米诺骨牌

UVa 11270 铺放骨牌(轮廓线DP)

Dumb Bones UVA - 10529(概率dp)

UVA 10529 - Dumb Bones(概率+区间dp)

poj 2411 Mondriaan's Dream 骨牌铺放 状压dp

G - Most Dangerous Shark dp 单调栈 单调队列