洛谷DP(暴力篇)

本文详细介绍了洛谷平台上涉及动态规划的几道经典题目,包括线性DP和区间DP的应用。通过分析题目背景、解题思路及代码实现,展示了如何运用DP解决实际问题,例如守望者的逃离、摆花问题、线段覆盖等。文章旨在帮助读者深入理解动态规划的技巧和策略。
摘要由CSDN通过智能技术生成

洛谷DP (暴力篇)

一、线性DP

[P1095 NOIP2007 普及组] 守望者的逃离 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

普通的线性dp,简单的分类讨论就好了

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long ll;
int m, s, t;

int main()
{
    scanf("%d%d%d", &m, &s, &t);
    int s1 = 0, s2 = 0;
    for(int i = 1; i <= t; i++)
    {
        s1 += 17;
        if(m >= 10) {s2 += 60; m -= 10;}
        else m += 4;
        s1 = max(s1, s2);
        if(s1 > s)
        {
            puts("Yes");
            printf("%d\n", i);
            return 0;
        }
    }
    puts("No");
    printf("%d\n", s1);

    return 0;
}

[P1077 NOIP2012 普及组] 摆花 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题意:有 n 个数 ( c 1 , c 2 , . . . , c n ) (c_1,c_2,...,c_n) c1,c2,...,cn 0 ⩽ c i ⩽ a i 0\leqslant c_i\leqslant a_i 0ciai,求有多少种方案数使 ∑ i = 1 n c i = m \sum\limits_{i=1}^nc_i=m i=1nci=m

定义状态:f(i, j) 表示前 i个数总和为 j 的方案数。

那么,易得状态转移方程: f ( i , j ) = ∑ k = 0 a i f ( i − 1 , j − k ) f(i, j) = \sum\limits_{k=0}^{a_{i}}f(i-1,j-k) f(i,j)=k=0aif(i1,jk)

k k k是枚举第 i i i个商品的数值。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long ll;
const int M = 105, N = 105, mod = 1e6 + 7;
int n, m;
int a[N];
ll f[N][M];

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    f[0][0] = 1;
    for(int i = 1; i <= n; i++)
    {
        for(int j = 0; j <= m; j++)
        {
            for(int k = 0; k <= a[i]; k++)
            {
                if(j >= k) f[i][j] = (f[i][j] + f[i - 1][j - k]) % mod;
            }
        }
    }
    printf("%lld\n", f[n][m]);
    return 0;
}

[P3842 TJOI2007]线段 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

f [ i ] [ 0 ] f[i][0] f[i][0]表示访问完第 i i i行以左端点结束, f [ i ] [ 1 ] f[i][1] f[i][1]表示访问完以右端点结束

记住最后行进到 ( n , n ) (n,n) (n,n)即可

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 2e4 + 50;
int n;
int f[N][2], l[N], r[N];

int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d%d", &l[i], &r[i]);
    }

    f[1][0] = r[1] + (r[1] - l[1]) - 1, f[1][1] = r[1] - 1;

    for(int i = 2; i <= n; i++)
    {
        f[i][0] = min(f[i - 1][0] + abs(r[i] - l[i - 1]) + r[i] - l[i] + 1, f[i - 1][1] + abs(r[i] - r[i - 1]) + r[i] - l[i] + 1);
        f[i][1] = min(f[i - 1][0] + abs(l[i] - l[i - 1]) + r[i] - l[i] + 1, f[i - 1][1] + abs(l[i] - r[i - 1]) + r[i] - l[i] + 1);
    }

    printf("%d\n", min(f[n][0] + (n - l[n]), f[n][1] + (n - r[n])));
    return 0;
}

[P1541 NOIP2010 提高组] 乌龟棋 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

暴力枚举,四个数字各用过几次,然后四种状态转移,每一次都取max即可

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN=41;
int F[MAXN][MAXN][MAXN][MAXN],num[351],g[5],n,m,x;
int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>num[i];
    F[0][0][0][0]=num[1];
    for(int i=1;i<=m;i++)
    {
        cin>>x;
        g[x]++;
    }
    for(int a=0;a<=g[1];a++)
        for(int b=0;b<=g[2];b++)
            for(int c=0;c<=g[3];c++)
                for(int d=0;d<=g[4];d++)
                {
                    int r=1+a+b*2+c*3+d*4;
                    if(a!=0)	F[a][b][c][d]=max(F[a][b][c][d],F[a-1][b][c][d]+num[r]);
                    if(b!=0)    F[a][b][c][d]=max(F[a][b][c][d],F[a][b-1][c][d]+num[r]);
                    if(c!=0)    F[a][b][c][d]=max(F[a][b][c][d],F[a][b][c-1][d]+num[r]);
                    if(d!=0)	F[a][b][c][d]=max(F[a][b][c][d],F[a][b][c][d-1]+num[r]);
                }	
    cout<<F[g[1]][g[2]][g[3]][g[4]];
    return 0;
}

二、区间DP

1.先枚举区间长度,再枚举 l l l,然后推出 r r r

2.如果是个环,开两倍数组长度。

3.求最大最小值,记得每一次对数组初始化为inf或-inf

[P3146 USACO16OPEN]248 G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

环形,改成两条链。

#include<bits/stdc++.h>
using namespace std;
const int N = 410, INF = 0x3f3f3f3f;

int f[N][N], g[N][N], s[N], w[N];
int n;
int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d", &w[i]);
        w[n + i] = w[i];
    }

    for(int i = 1; i <= 2 * n; i++) s[i] = s[i - 1] + w[i];
    memset(f, 0x3f, sizeof f);
    memset(g, -0x3f, sizeof g);
    for(int i = 1; i <= 2 * n; i++) f[i][i] = g[i][i] = 0;

    for(int len = 2; len <= n; len++)
    {
        for(int l = 1, r = l + len - 1; r <= 2 * n; l++, r++)
        {
            for(int k = l; k < r; k++)
            {
                f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]);
                g[l][r] = max(g[l][r], g[l][k] + g[k + 1][r] + s[r] - s[l - 1]);
            }
        }
    }

    int res1 = INF, res2 = -INF;
    for(int l = 1, r = l + n - 1; r <= 2 * n; l++, r++)
    {
        res1 = min(res1, f[l][r]);
        res2 = max(res2, g[l][r]);
    }
    printf("%d\n%d\n", res1, res2);
    return 0;
}

[P3146 USACO16OPEN]248 G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

区间DP先枚举区间长度 l e n len len

f [ l ] [ r ] f[l][r] f[l][r]能合并当且仅当存在一个 k k k,使得 f [ l ] [ k ] = f [ k + 1 ] [ r ]   a n d   f [ l ] [ k ] ! = 0 f[l][k]=f[k + 1][r] \ and \ f[l][k] != 0 f[l][k]=f[k+1][r] and f[l][k]!=0

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
const int MAXN = 250;
int n;
int a[MAXN];
int f[MAXN][MAXN];

int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    int maxx = 0;
    for(int i = 1; i <= n; i++)
    {
        maxx = max(maxx, a[i]);
        f[i][i] = a[i];
    }

    for(int len = 2; len <= n; len ++)
    {
        for(int l = 1; l + len - 1 <= n; l ++)
        {
            int r = l + len - 1;
            for(int k = l; k < r; k++)
            {
                if(f[l][k] == f[k + 1][r] && f[l][k] && f[k + 1][r])
                {
                    f[l][r] = max(f[l][r], f[l][k] + 1);
                    maxx = max(maxx, f[l][r]);
                }
            }
        }
    }
    printf("%d\n", maxx);

    return 0;
}

记录详情 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

断环成链,注意状态转移的细节

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
const int MAXN = 105;
int n;
int a[2 * MAXN];
int f[2 * MAXN][2 * MAXN];

int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
        a[i + n] = a[i];
    }

    for(int len = 2; len <= n + 1; len ++)
    {
        for(int l = 1; l + len - 1 <= n + n; l ++)
        {
            int r = l + len - 1;
            for(int k = l + 1; k <= r - 1; k ++)
            {
                f[l][r] = max(f[l][r], f[l][k] + f[k][r] + a[l] * a[k] * a[r]);
            }
        }
    }

    int maxx = 0;
    for(int i = 1; i <= n; i++)
    {
        maxx = max(maxx, f[i][i + n]);
    }
    printf("%d\n", maxx);

    return 0;
}

[P4342 IOI1998]Polygon - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

断环成链,注意每一次转移的全部情况。

#include<bits/stdc++.h>
#define lcy AKIOI
#define ll long long
const int inf=0x3f3f3f3f;
int n,ans=-inf;
int a[105];
int f[150][150],g[150][150];
char c[105];
int max(int x,int y){return (x>y)?(x):(y);}
int min(int x,int y){return (x<y)?(x):(y);}
int main(){
    scanf("%d\n",&n);//读入很诡异
    for(int i=1;i<=n;i++){
        scanf("%c %d",&c[i],&a[i]);getchar();
        a[n+i]=a[i];c[n+i]=c[i];//断环为链
    }
    for(int i=1;i<=(n<<1);i++){
        for(int j=1;j<=(n<<1);j++){
            f[i][j]=-inf,g[i][j]=inf;
        }
    }
    for(int i=1;i<=(n<<1);i++)f[i][i]=g[i][i]=a[i];
    for(int len=2;len<=n;len++){
        for(int i=1,j=len;j<=(n<<1);i++,j++){
            for(int k=i;k<j;k++){
                if(c[k+1]=='x'){
                    f[i][j]=max(f[i][j],max(f[i][k]*f[k+1][j],max(g[i][k]*g[k+1][j],max(f[i][k]*g[k+1][j],g[i][k]*f[k+1][j]))));
                    g[i][j]=min(g[i][j],min(f[i][k]*f[k+1][j],min(g[i][k]*g[k+1][j],min(f[i][k]*g[k+1][j],g[i][k]*f[k+1][j]))));
                }
                else if(c[k+1]=='t'){
                    f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]);
                    g[i][j]=min(g[i][j],g[i][k]+g[k+1][j]);
                }
            }
        }
    }
    for(int i=1;i<=n;i++)ans=max(ans,f[i][i+n-1]);printf("%d\n",ans);
    for(int i=1;i<=n;i++)if(f[i][i+n-1]==ans)printf("%d ",i);
    return 0;
}

CF149D Coloring Brackets - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题意:给出一个配对的括号序列(如"(())()"、"()“等, “)()”、”(()"是不符合要求的 ),对该序列按以下方法进行染色: 1.一个括号可以染红色、蓝色或不染色 2.一对匹配的括号需要且只能将其中一个染色 3.相邻两个括号颜色不能相同(但可以都不染色) 求符合条件的染色方案数

设计 dp[l][r][p][q] 为将区间$ [l,r]$内染色的总方案数,且最左边的括号颜色为 p,最右边的括号颜色为 q。

其中 p,q 的值为 0 时代表不染色,为 1 时代表蓝色,为 2 时代表红色。

d p [ l ] [ r ] [ 0 ] [ 1 ] = d p [ l ] [ r ] [ 0 ] [ 2 ] = d p [ l ] [ r ] [ 1 ] [ 0 ] = d p [ l ] [ r ] [ 2 ] [ 0 ] = 1 ; dp[l][r][0][1]=dp[l][r][0][2]=dp[l][r][1][0]=dp[l][r][2][0]=1; dp[l][r][0][1]=dp[l][r][0][2]=dp[l][r][1][0]=dp[l][r][2][0]=1;

由于「一对匹配的括号需要且只能将其中一个染色」

dp[l][r][0][0]=dp[l][r][1][2]=dp[l][r][2][1]=dp[l][r][1][1]=dp[l][r][2][2]=0

  • 如果第 l 个括号刚好和第 r 个括号配对:

那么这个区间类似于这样:(......)

所以易知 dp[l][r][0][0]=dp[l][r][1][2]=dp[l][r][2][1]=dp[l][r][1][1]=dp[l][r][2][2]=0。原因和上面一样。

所以这五个就不需要再转移了。

至于剩下的四种情况,以 dp[l][r][0][1] 为例:

由于左端点不染色,右端点是 1
所以它不能 dp[l+1][r-1][·][1] 转移过来。
否则相邻两个括号的颜色相同,就不符合条件了。

那么我们从 0 到 2 枚举 i,j,然后看 i,j 的值来一个一个转移即可。

  • 如果第 l 个括号刚好和第 r 个括号不配对:

那么我们找出与第 l 个括号配对的括号。

区间大概是这样的:(......)...(

那么我们分别处理出区间$ [l,\texttt{right[l]}]$ 的方案数和区间 [ right[l] + 1 , r ] [\texttt{right[l]}+1,r] [right[l]+1,r] 的方案数,然后相乘即可。

需要注意的是, right[l] \texttt{right[l]} right[l] right[l] + 1 \texttt{right[l]}+1 right[l]+1 处的括号颜色不能相同。这种情况需要特判。

有一个需要注意的点就是:转移顺序最好是以记忆化搜索的顺序来,不然的话会很麻烦。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <stack>

using namespace std;
typedef long long ll;
const int N = 705, mod = 1e9 + 7;
char s[N];
ll match[N], dp[N][N][3][3];
stack<int>st;

void dfs(int l, int r)
{
    if(r == l + 1)
    {
        dp[l][r][0][1] = dp[l][r][0][2] = dp[l][r][1][0] = dp[l][r][2][0] = 1;
    }
    else if(r == match[l])
    {
        dfs(l + 1, r - 1);
        for(int i = 0; i <= 2; i++)
        {
            for(int j = 0; j <= 2; j++)
            {
                if(j != 1) dp[l][r][0][1] = (dp[l][r][0][1] + dp[l + 1][r - 1][i][j]) % mod;
                if(j != 2) dp[l][r][0][2] = (dp[l][r][0][2] + dp[l + 1][r - 1][i][j]) % mod;
                if(i != 1) dp[l][r][1][0] = (dp[l][r][1][0] + dp[l + 1][r - 1][i][j]) % mod;
                if(i != 2) dp[l][r][2][0] = (dp[l][r][2][0] + dp[l + 1][r - 1][i][j]) % mod;
            }
        }
    }
    else{
        dfs(l, match[l]), dfs(match[l] + 1, r);
        for(int i = 0; i <= 2; i++)
        {
            for(int j = 0; j <= 2; j++)
            {
                for(int x = 0; x <= 2; x++)
                {
                    for(int y = 0; y <= 2; y++)
                    {
                        if((j == 1 && x == 1) || (j == 2 && x == 2)) continue;
                        dp[l][r][i][y] = (dp[l][r][i][y] + dp[l][match[l]][i][j] * dp[match[l] + 1][r][x][y] % mod) % mod;
                    }
                }
            }
        }
    }
}

int main()
{
    scanf("%s", s + 1);
    int n = strlen(s + 1);

    for(int i = 1; i <= n; i++)
    {
        if(s[i] == '(') st.push(i);
        else
        {
            match[i] = st.top();
            match[st.top()] = i;
            st.pop();
        }
    }

    dfs(1, n);
    ll ans = 0;
    for(int i = 0; i <= 2; i++)
    {
        for(int j = 0; j <= 2; j++)
        {
            ans = (ans + dp[1][n][i][j]) % mod;
        }
    }
    printf("%lld\n", ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值