基础动态规划(递推、计数等)| 做题记录

小编脑子不好使,dp题做得少,被迫加强训练(指做水题)。

洛谷P1026 统计单词个数

dp[i][j]为前i个字符且被划分为了j块的最大单词数

状态转移方程:

dp[i][j]=max{dp[i][j],dp[l][j]+sum[l+1][i]}(j1l<i)

其中sum[i][j]表示从i到j的单词数

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

const int maxn = 10;
std::string s, ch, a[10];
int p, k, m;
int dp[200 + 10][50], sum[200 + 10][200 + 10];

bool check(int l, int r) {
    std::string t = ch.substr(l, r - l + 1);
    for (int i = 1; i <= m; i++) {
        if (t.find(a[i]) == 0) return true;
    }
    return false;
}

int main() {
    scanf("%d%d", &p, &k);
    ch += '0';
    while (p--) {
        std::cin >> s;
        ch += s;
    }
    int len = ch.length() - 1;
    scanf("%d", &m);
    for (int i = 1; i <= m; i++) std::cin >> a[i];
    for (int i = len; i >= 1; i--)//i~j的字符串匹配单词
        for (int j = i; j >= 1; j--) {
            sum[j][i] = sum[j + 1][i];
            if (check(j, i)) sum[j][i]++;
        }
    dp[0][0] = 0;
    for (int i = 1; i <= len; i++)
        for (int j = 1; j <= i && j <= k; j++)
            for (int q = j - 1; q < i; q++)
                dp[i][j] = std::max(dp[i][j], dp[q][j - 1] + sum[q + 1][i]);

    printf("%d", dp[len][k]);
}

洛谷P1057 传球游戏

dp[i][j]为传了i次球,当前在第j个人处的方案数

状态转移方程:

dp[i][j]=dp[i1][j1]+dp[i1][j+1]

注意处理j=1和j=n时的状态转移方程。

#include<iostream>
#include<cstdio>

const int maxn = 30 + 5;
int n, m;
int dp[maxn][maxn];//传了i次,当前在第j个人
int main() {
    scanf("%d%d", &n, &m);
    dp[0][1] = 1;
    for (int i = 1; i <= m; i++)
        for (int j = 1; j <= n; j++) {
            if (j == 1) dp[i][j] = dp[i - 1][2] + dp[i - 1][n];
            else if (j == n) dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][1];
            else dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j + 1];
        }
    printf("%d", dp[m][1]);
}

CF1739C Card Game

f[n]为n张牌时先手必胜的方案数。

所有方案数为C[n][n/2],首先计算先手必胜的方案数。

  • 我方摸到最大数:必胜,此时剩余牌的方案数是C[n1][n/21]

  • 对手摸到最大数:

    • 对手摸到次大数,此时对手可以用次大数接住我方任何一种牌,下一回合出最大数,必败。
    • 我方摸到次大数:
      • 对手摸到第三大数。此时我方若出较小的数,对手用第三大数接。下一轮对手出最大数,输;我方若出次大数,对手用最大数接,下一轮对手出第三大数,输。总结:必输。
      • 我方摸到第三大数:
        • 我方摸到第四大数。此时我方出【次大数、第三大数、第四大数】其中一种牌,对手只用最大数接。下一轮对手的牌我方可以剩余两个牌中的一个接,必胜。此时剩余牌方案数为C[n4][n/23]
        • 对手摸到第四大数。此时我方可以用最大数接住任一个数,下一轮我方出第四大叔,对方用剩余的数接,此时四张牌全部打空,问题变为n-4的情况,重复以上步骤

可以得知,

f[n]=C[n1][n/21]+C[n4][n/23]+f[n4]

平局只有一种方案,先手必败的情况用总方案数-平局方案数-先手必胜方案数。

预处理组合数即可。

#include<iostream>
#include<cstdio>
const int mod=998244353;
const int maxn=100;
int T;
int n;

long long C[maxn][maxn],f[maxn];
void init(){
    for(int i=0;i<70;i++)
        for(int j=0;j<=i;j++){
            if(!j) C[i][j]=1;
            else C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
        }
    f[2]=1;//有个2
    f[4]=3;//有个4
    for(int i=6;i<=60;i+=2){
        f[i]=(C[i-1][i/2-1]+C[i-4][i/2-3]+f[i-4])%mod;
    }
}
//f[i]=C[n/2][3]+C[n/2][1]+f[i-4]
int main(){
    scanf("%d",&T);
    init();
    while(T--){
        scanf("%d",&n);
        printf("%lld %lld %lld\n",f[n],((C[n][n/2]-f[n]-1)%mod+mod)%mod,1);
    }
}

洛谷P1216 Number Triangles

dp[i][j]表示在第i层的第j个数

状态转移方程:

dp[i][j]=max{dp[i1][j],dp[i1][j1]}+a[i][j](1ji)

注意j=1j=i时的特判就行。

#include<iostream>
#include<cstdio>

const int maxn = 1000 + 10;
int n, a[maxn][maxn], dp[maxn][maxn], ans;

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= i; j++)
            scanf("%d", &a[i][j]);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= i; j++) {
            if (j == 1) dp[i][j] = dp[i - 1][j] + a[i][j];
            else if (j == i) dp[i][j] = dp[i - 1][j - 1] + a[i][j];
            else dp[i][j] = std::max(dp[i - 1][j], dp[i - 1][j - 1]) + a[i][j];
        }
    for (int i = 1; i <= n; i++) ans = std::max(ans, dp[n][i]);
    printf("%d", ans);
    return 0;
}

洛谷P1077 摆花

很经典的计数dp+滚动数组优化

dp[i][j]表示选了i种花,花总数为j时的方案数

状态转移方程:

dp[i][j]=k=0aidp[i1][jk](kj)

因为第一维只涉及到ii1,果断滚动数组降维。

另外这题m范围很小,如果m达到了2e5那就得参考这题做了...

#include<iostream>
#include<cstdio>

const int mod = 1e6 + 7;
const int maxn = 100 + 10;
int n, m, dp[maxn], a[maxn];

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

    printf("%d", dp[m]);
    return 0;
}

洛谷P1115 最大子段和

裸的最大子段和问题。

dp[i]为以i为结尾的最大子段和。

转移方程:

dp[i]={dp[i1]+a[i],dp[i1]>=0a[i],dp[i1]<0

#include<iostream>
#include<cstdio>

const int maxn = 2e5 + 7;
int a[maxn], n, dp[maxn];
int res = -11451419;

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    //res=dp[1]=a[1];
    for (int i = 1; i <= n; i++) {
        if (dp[i - 1] >= 0) dp[i] = dp[i - 1] + a[i];
        else dp[i] = a[i];
        res = std::max(res, dp[i]);
    }
    printf("%d", res);
    return 0;
}

洛谷P1095 守望者的逃离

首先预处理仅用魔法跑路的dp状态。

再枚举时间,判断当前走路更优还是按照旧状态更优。

#include<iostream>
#include<cstdio>

const int maxn = 3e5 + 10;
int M, S, T;
int dp[maxn];
bool flag = 0;

int main() {
    scanf("%d%d%d", &M, &S, &T);
    for (int i = 1; i <= T; i++) {
        if (M >= 10) dp[i] = dp[i - 1] + 60, M -= 10;
        else M += 4, dp[i] = dp[i - 1];
    }
    for (int i = 1; i <= T; i++) {
        if (dp[i] < dp[i - 1] + 17) dp[i] = dp[i - 1] + 17;
        if (dp[i] >= S) {
            puts("Yes");
            printf("%d", i);
            return 0;
        }
    }
    puts("No");
    printf("%d", dp[T]);
    return 0;
}

NOIP2016 金明的预算方案

在01背包的基础上,增加几个状态,具体为:

1.不拿

2.只拿主件

3.只拿主件+附件1

4.只拿主件+附件2

5.都拿

剩下的就是模板了。

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

const int maxn = 4e4 + 10;
int w[maxn], w2[maxn][5], val[maxn], val2[maxn][5], dp[maxn];
int cnt[maxn];
int n, m;
int v[maxn], p[maxn], q[maxn];

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++) {
        scanf("%d%d%d", &v[i], &p[i], &q[i]);
        if (q[i] == 0) {
            cnt[i] = 0;
            w[i] = v[i];
            val[i] = v[i] * p[i];
        } else {
            w2[q[i]][++cnt[q[i]]] = v[i];
            val2[q[i]][cnt[q[i]]] = v[i] * p[i];
        }
    }
    for (int i = 1; i <= m; i++)
        for (int j = n; j >= w[i]; j--) {
            dp[j] = std::max(dp[j], dp[j - w[i]] + val[i]);
            if (j >= w[i] + w2[i][1]) dp[j] = std::max(dp[j], dp[j - (w[i] + w2[i][1])] + val[i] + val2[i][1]);
            if (j >= w[i] + w2[i][2]) dp[j] = std::max(dp[j], dp[j - (w[i] + w2[i][2])] + val[i] + val2[i][2]);
            if (j >= w[i] + w2[i][1] + w2[i][2])
                dp[j] = std::max(dp[j], dp[j - (w[i] + w2[i][1] + w2[i][2])] + val[i] + val2[i][1] + val2[i][2]);
        }
    printf("%d", dp[n]);
    return 0;

}

#ifdef _DEBUG
for(int i=1;i<=m;i++){
        if(!cnt[i]) printf("%d %d\n",w[i][cnt[i]],val[i][cnt[i]]);
        else
            for(int j=1;j<=cnt[i];j++)
                printf("%d %d:%d %d\n",i,j,w[i][j],val[i][j]);
    }
#endif
posted @   SxtoxA  阅读(12)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
12 13
点击右上角即可分享
微信分享提示