DP刷题记录

美丽序列

https://ac.nowcoder.com/acm/problem/21313?&headNav=acm

题目描述

牛牛喜欢整数序列,他认为一个序列美丽的定义是
1:每个数都在0到40之间
2:每个数都小于等于之前的数的平均值
具体地说:for each i, 1 <= i < N, A[i] <= (A[0] + A[1] + ... + A[i-1]) / i.
3:没有三个连续的递减的数

现在给你一个序列,每个元素是-1到40,你可以将序列中的-1修改成任意的数,求你可以得到多少个美丽序列,答案对1e9+7取模

输入描述:

第一行输入一个整数n (1 ≤ n ≤ 40)
第二行输入n个整数

输出描述:

输出一个整数

示例1

输入
2
3 -1
输出
4

示例2

输入
3
5 3 -1
输出
2

示例3

输入
3
-1 0 40
输出
0

示例4

输入
11
-1 40 -1 -1 -1 10 -1 -1 -1 21 -1
输出
579347890

思路

这个题主要是状态的表示:
f[i][j][k][w]:第i位数字为j,第j位数字为k,和为w的方案数。
记录这几个状态就可以转移了。但是你发现初始化麻烦和复杂度很高O(n^6)
如果我们把第3维k的含义表示为:递减的长度
初始化方便,而且复杂度O(n^5)

#include<bits/stdc++.h>
#define LL long long
using namespace std;

LL f[41][41][4][1601];
int a[45];
const int mod=1e9+7;
int main() {

    int n;
    scanf("%d", &n);
    for(int i=1; i<=n; i++) {
        scanf("%d", &a[i]);
    }
    f[0][0][1][0]=1;
    for(int i=0; i<n; i++) {
        for(int j=0; j<=40; j++) {
            for(int k=1; k<=2; k++) {
                for(int w=0; w<=i*40; w++) {
                    if(f[i][j][k][w]) {
                        if(a[i+1]==-1) {
                            for(int z=0; z<=40; z++) {
                                int pos=i?(w/i):40;
                                if(z<=pos) {
                                    if(z<j) {
                                        f[i+1][z][k+1][w+z]+=f[i][j][k][w];
                                        f[i+1][z][k+1][w+z]%=mod;
                                    } else {
                                        f[i+1][z][1][w+z]+=f[i][j][k][w];
                                        f[i+1][z][1][w+z]%=mod;
                                    }
                                }
                            }
                        } else {
                            int pos=i?(w/i):40;
                            if(a[i+1]<=pos) {
                                if(a[i+1]<j) {
                                    f[i+1][a[i+1]][k+1][w+a[i+1]]+=f[i][j][k][w];
                                    f[i+1][a[i+1]][k+1][w+a[i+1]]%=mod;
                                } else {
                                    f[i+1][a[i+1]][1][w+a[i+1]]+=f[i][j][k][w];
                                    f[i+1][a[i+1]][1][w+a[i+1]]%=mod;
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    int ans=0;
    for(int i=0; i<=40; i++) {
        for(int j=1; j<=2; j++) {
            for(int k=0; k<=40*n; k++) {
                ans+=f[n][i][j][k];
                ans%=mod;
            }
        }
    }
    printf("%d\n", ans);


    return 0;
}

排序背包

题目描述

牛牛正在打一场CF
比赛时间为T分钟,有N道题,可以在比赛时间内的任意时间提交代码
第i道题的分数为maxPoints[i],题目的分数随着比赛的进行,每分钟减少pointsPerMinute[i]
这是一场比较dark的Cf,分数可能减成负数
已知第i道题需要花费 requiredTime[i] 的时间解决
请问最多可以得到多少分

输入描述:

第一行输入两个整数N,T (1 ≤ N ≤ 50, 1 ≤ T ≤ 100000)
第二行输入n个整数maxPoints[i]
第三行输入n个整数pointsPerMinute[i]
第四行输入n个整数requiredTime[i]
1 ≤ maxPoints[i],pointsPerMinute[i],requiredTime[i] ≤ 100000

输出描述:

输出一个整数

示例1

输入
1 74
502
2
47
输出
408

示例2

输入
复制
2 40000
100000 100000
1 100000
50000 30000
输出
0

示例3

输入
3 75
250 500 1000
2 4 8
25 25 25
输出
1200

示例4

输入
3 30
100 100 100000
1 1 100
15 15 30
输出
97000

备注:

子任务1: n <= 10
子任务2: n <= 20
子任务3: 无限制

思路:

假设最佳方案有x题和y题
如果x题先写:
s1=maxPoints[x]requiredTime[x]pointsPerMinute[x]+maxPoints[y](requiredTime[x]+requiredTime[y])pointsPerMinute[y]
如果y题先写:
s2=maxPoints[y]requiredTime[y]pointsPerMinute[y]+maxPoints[x](requiredTime[x]+requiredTime[y])pointsPerMinute[x]
假设x题在前面你们就要满足:
s1<=s2化简后对题目排序就可以了。然后跑背包

#include<bits/stdc++.h>
#define LL long long
using namespace std;

struct Node{
    LL w, a, t;
}a[55];

LL f[100005];
int main() {

    int n, T; scanf("%d%d", &n, &T);
    for(int i=1; i<=n; i++){
        scanf("%lld", &a[i].w);
    }
    for(int i=1; i<=n; i++){
        scanf("%lld", &a[i].a);
    }
    for(int i=1; i<=n; i++){
        scanf("%lld", &a[i].t);
    }
    sort(a+1, a+1+n, [](Node &a, Node &b){return a.t*b.a<b.t*a.a;});
    for(int i=1; i<=n; i++){
        for(int k=T; k>=a[i].t; k--){
            f[k]=max(f[k], f[k-a[i].t]-k*a[i].a+a[i].w);
        }
    }
    printf("%lld\n", *max_element(f, f+T+1));


    return 0;
}

贪心+概率DP(网格游戏 开格子)

https://ac.nowcoder.com/acm/problem/21797

题目描述

有一个游戏平板上面有n×m个格子,一开始每个格子都是关闭的,每个格子里面都有一个标记
已知每种标记恰好出现两次,也就是一共有n*m/2种标记
规定一次移动为依次(one by one不是同时)打开一对格子查看里面的标记,如果标记不一样,格子会自动关闭,但是你的记忆是超强了,看过了就不会忘,如果标记是一样的,格子从此就一直保持打开状态,当所有格子都打开时游戏结束
请算出游戏结束的最少期望步数

输入描述:

输入一行包含两个整数 N, M (1 ≤ N ≤ 50, 1 ≤ M ≤ 50)
N*M是偶数
输出描述:
输出一个浮点数.误差不超过1e-9

示例1

输入
1 2
输出
1.0

示例2

输入
2 2
输出
2.6666666666666665

示例3

输入
2 3
输出
4.333333333333334

示例4

输入
4 4
输出
12.392984792984793

备注:

子任务1: n * m <= 100
子任务2: n * m <= 1000
子任务3: 无限制

思路

我们能够很快想到思路:
DP(x, y):已经打开x个,有y个没有匹配的期望步数
那么答案就是DP(0, 0)
考虑转移:因为一次可以打开2个格子,并且格子不是同时打开了,那么我们可以打开一个再选择最佳方案。
当前状态我们肯定要打开格子:

  • 如果打开的第一个格子是之前没有匹配的P=y/(N-a)
    那么下次肯定是打开之前没有匹配的格子。
    f[x][y]=PDP(x+1,y1)
  • 如果打开的第一个格子是之前没有出现过的(1-P)
    那么打开第二个格子有3种情况
    1.和刚打开的格子匹配f[x][y]+=(1P)(1/(Na1))DP(x+1,y)
    2.和之前没有匹配的格子匹配,那么下次肯定是打开这两个格子f[x][y]+=(1P)(y/(Na1))(DP(x+2,y)+1)
    3.和所有的格子不匹配f[x][y]+=(1P)(1y/(Na1)1/(Na1))DP(x+2,y+2)
    最后f[x][y]+=1,因为这次打开了格子。
    记忆化DP就可以了
#pragma comment(linker, "/STACK:102400000,102400000")
#include <bits/stdc++.h>
#define LL long long
using namespace std;

int N;
double f[2501][1251];
bool vis[2501][1251];
double DP(int a, int b){
    if(b<0) return 0;
    if(a>=N) return 0;
    if(b>N-a) return 0;
    if(vis[a][b]) return f[a][b];
    vis[a][b]=1;
    LL x=N-a;
    double p=b*1.0/(x);
    double ans=DP(a+1, b-1)*p;
    if(x>1){
        double p1=1.0*(1)/(x-1);
        double p2=1.0*(b)/(x-1);
        double p3=(1.0-p1-p2);
        ans+=(1-p)*p1*DP(a+2, b)+(1-p)*p2*(DP(a+2, b)+1)+(1-p)*p3*DP(a+2, b+2);
    }
    return f[a][b]=ans+1;
}

int main(){

    int n, m; scanf("%d%d", &n, &m);
    N=n*m;
    printf("%.11f\n", DP(0, 0));

    return 0;
}

DP+博弈(棋盘的必胜策略)

题目描述

有一个二维棋盘,棋盘有r行c列,棋盘中的每一个位置有如下四种情况
'E': 表示出口,可能有多个
'T': 只有一个,表示起点
'#': 表示障碍
'.': 表示空地

牛牛和牛妹在这样一个棋盘上玩游戏,他们有一张写有整数k的卡片,一开始放置在起点的位置,现在牛牛和牛妹开始轮流操作,牛牛先操作
当前操作的牛会选择上下左右其中一个方向移动卡片,每走一步,卡片上的数字减去1
只能走到空地上, 或者走到出口,走到出口,游戏就会结束,卡片的数字变成0的时候游戏也会结束,不能再移动的牛会输掉游戏

如果牛牛和牛妹都用最佳策略,请问谁会赢

输入描述

第一行输入3个整数r,c,k
接下来r行每行读入k个字符表示棋盘
1 ≤ r,c ≤ 50, 1 ≤ k ≤ 100

输出描述

如果牛牛有必胜策略,输出"niuniu"
否则输出"niumei"

示例1

输入

2 3 3
T.#
#.E

输出
niuniu

示例2

输入

3 3 99
E#E
#T#
E#E

输出
niumei

备注:

子任务1:mac(r,c) <= 10
子任务2:max(r,c) <= 20
子任务3:无限制
思路:
f[x][y][k]:在(i, j)数字为k的先手的胜负态.
结果sg定理转移就可以了。

#include<bits/stdc++.h>
using namespace std;

char s[55][55];
int f[55][55][105];
int xx[4]={1, -1, 0, 0};
int yy[4]={0, 0, 1, -1};
int r, c;
int sg(int i, int j, int k){
    if(s[i][j]=='E'||k==0) return 0;
    if(f[i][j][k]!=-1) return f[i][j][k];
    f[i][j][k]=0;
    for(int kk=0; kk<4; kk++){
        int x=i+xx[kk], y=j+yy[kk];
        if(x>=1&&x<=r&&y>=1&&y<=c&&s[x][y]!='#'){
            if(!sg(x, y, k-1)) f[i][j][k]=1;
        }
    }
    return f[i][j][k];
}

int main(){

    memset(f, -1, sizeof(f));
    int k; scanf("%d%d%d", &r, &c, &k);
    for(int i=1; i<=r; i++){
        scanf("%s", s[i]+1);
    }
    int x=0, y=0;
    for(int i=1; i<=r; i++){
        for(int j=1; j<=c; j++){
            if(s[i][j]=='T'){
                x=i, y=j;
            }
        }
    }
    if(sg(x, y, k)){
        printf("niuniu\n");
    }
    else{
        printf("niumei\n");
    }

	return 0;
}

和与或(数位+状压)

https://ac.nowcoder.com/acm/problem/21336

题目描述

给你一个数组R,包含N个元素,求有多少满足条件的序列A使得
0 ≤ A[i] ≤ R[i]
A[0]+A[1]+...+A[N-1]=A[0] or A[1]... or A[N-1]
输出答案对1e9+9取模

输入描述:

第一行输入一个整数N (2 ≤ N ≤ 10)
第二行输入N个整数 R[i] (1 ≤ R[i] ≤ 1e18)

输出描述:

输出一个整数

示例1

输入
2
3 5
输出
15

示例2

输入
3
3 3 3
输出
16

示例4

输入
4
26 74 25 30
输出
8409

示例5

输入
2
1000000000 1000000000
输出
420352509

思路

根据条件我们可以推出来:每个A[i]在二进制下占的位不能重复。
n<=1e18,在二进制下就60位,我们考虑数位DP。
DP(pos, bool x1, bool x2, ... bool xn)
考虑到第pos位,每个个A[i]是否有限制。
但是n=10,所以x数组用int状压一下。
转移时,第i位考虑为0,或者为1。为1时把这位1分配到哪个A[i]上。是否限制,考虑完全就可以了。

#include <bits/stdc++.h>
#define LL long long
using namespace std;

LL a[100005];
LL f[70][1025];
const int mod=1e9+9;
int n;
int dfs(int pos, int ok){
    if(pos==-1) return 1;
    LL &ans=f[pos][ok];
    if(ans>=0) return ans;
    int is=0;
    for(int i=0; i<n; i++){
        //如果pos为0,所有的A[i]的限制变化
        if(ok&(1<<i)&&!(a[i]&(1ll<<pos))) is^=1<<i;
    }
    ans=dfs(pos-1, is);//pos为0时
    //枚举把pos位1分配给谁
    for(int i=0; i<n; i++){
        if(!(ok&1<<i)||a[i]&(1ll<<pos)){
            ans+=dfs(pos-1, (ok&1<<i)&&a[i]&(1ll<<pos)?(is^1<<i):is);
        }
        ans%=mod;
    }
    return ans;
}

int main(){

    scanf("%d", &n);
    for(int i=0; i<n; i++){
        scanf("%lld", &a[i]);
    }
    memset(f, -1, sizeof(f));
    printf("%d\n", dfs(60, (1<<n)-1));

    return 0;
}

牛牛的计算机内存(状压DP)

https://ac.nowcoder.com/acm/problem/21873

题目描述

牛牛的计算机一共有m块内存,现在有n条指令,每条指令是一个01序列
如果指令的第i个字符为1,说明这条指令需要访问第i块内存
每条指令的执行代价为k^2, k为新访问内存的数量,即之前的指令都没有访问到的内存数量
你可以随意安排n条指令的执行顺序,求执行完所有指令的最小代价

输入描述:

第一行先输入一个整数n (1≤n≤20)
接下来每行输入一个01字符串,长度不超过20

输出描述:

输出一个整数

示例1

输入
3 3
111
001
010
输出
3

示例2

输入
5 5
11101
00111
10101
00000
11000
输出
9

示例3

输入
3 4
1000
1100
1110
输出
3

思路

n<=20,因为要考虑顺序,我们自然想到状压dp,f[s]:把s集合的指令执行完的最小代价。
转移时我们要知道s集合占用了哪些内容,我们另外用一个数组ok[s]:二进制状压s集合的指令执行完后,
的内容访问状态。
ok[s]:可以转移时确定,也可以预处理。

#include<bits/stdc++.h>
#define LL long long
using namespace std;

char a[25][25];
int num[25];
int ok[1<<22];
int f[1<<22];
int n, m;
void getok(int i, int s1, int s2){
    if(i==n){
        ok[s1]=s2;
        return ;
    }
    getok(i+1, s1|(1<<i), s2|num[i]);
    getok(i+1, s1, s2);
}

int sum(int s1, int s2){
    int res=0;
    for(int i=0; i<m; i++){
        if((s1&(1<<i))&&!(s2&(1<<i))){
            res++;
        }
    }
    return res*res;
}

int main(){

    scanf("%d%d", &n, &m);
    for(int i=0; i<n; i++){
        scanf("%s", a[i]+1);
        int s=0;
        for(int k=1; k<=m; k++){
            s=s*2+a[i][k]-'0';
        }
        num[i]=s;
    }
    getok(0, 0, 0);
    memset(f, 0x3f, sizeof(f));
    f[0]=0;
    for(int s=1; s<(1<<n); s++){
        for(int i=0; i<n; i++){
            if(s&(1<<i)){
                f[s]=min(f[s], f[s^(1<<i)]+sum(num[i], ok[s^(1<<i)]));
            }
        }
    }
    printf("%d\n", f[(1<<n)-1]);

	return 0;
}

牛牛去买球

https://ac.nowcoder.com/acm/problem/21668

题目描述

一个人如果在他很小的时候就自己赚过钱,他的一生都会过得非常节俭,因为他知道财富来之不易.

作为一个勤俭节约的好孩子,牛牛决定在生活中践行这一原则.

有一天他想去商店买一些球来玩,他发现商店里有n个盒子,每个盒子外面有一张标签告诉你有ai个红球,bi个蓝球,需要ci的钱购买

但是由于店主是一个粗心的人,他告诉你每个盒子球的总量是符合标签的说明的,但是具体的种类可能会有如下偏差,比如可能有

(ai+1 ,bi-1),(ai, bi), (ai-1, bi+1)三种可能

牛牛 想要买至少K个同颜色的球,但是他又不想浪费钱.

帮他算算最少花多少钱买盒子能够使得至少会有K个球是同色的

输入描述:

第一行输入两个整数n,K (1≤ n≤50, 1 ≤ K ≤ 10000)

第二行输入n个整数表示a数组

第三行输入n个整数表示b数组

第三行输入n个整数表示c 数组

1 ≤ ai,bi,ci ≤ 10000

输出描述:

输出一个整数,如果无法达成目的,输出1

示例1

输入
2 10
6 5
4 4
1 1
输出
2

示例2

输入
2 10
5 5
4 4
1 1
输出
-1

示例3

输入
1 9
10
5
13
输出
13

示例4

输入
5 10
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
输出
10

示例5

输入
5 17
1 2 3 4 15
1 2 3 4 5
1 2 3 4 5
输出
9

思路

我们考虑2个极端的情况:
1.我们选择a选到k,并且每个盒子都是A[i]-1
2.我们选择b选到k,并且每个盒子都是B[i]-1
当然这个思路一些问题。因为都是A[i]-1的时候,可能选择b更快。
我们考虑什么时候,一定有k个一种类型的球。
min(max(x, y))=k。那么x+y=2k-1。
所以我们选择球的总数>=2k-1时,一定可行。
所以跑3个背包就可以了。

#include<bits/stdc++.h>
#define LL long long
using namespace std;

int a[55], b[55], c[55];
int f[20005];
int main(){

    int n, k; scanf("%d%d", &n, &k);
    for(int i=1; i<=n; i++) scanf("%d", &a[i]);
    for(int i=1; i<=n; i++) scanf("%d", &b[i]);
    for(int i=1; i<=n; i++) scanf("%d", &c[i]);

    
    memset(f, 0x3f, sizeof(f));f[0]=0;
    int ans=f[1];
    
    for(int i=1; i<=n; i++){
        for(int j=20000; j>=a[i]-1; j--){
            f[j]=min(f[j], f[j-(a[i]-1)]+c[i]);
        }
    }
    for(int i=k; i<=20000; i++) ans=min(ans, f[i]);

    memset(f, 0x3f, sizeof(f));f[0]=0;
    for(int i=1; i<=n; i++){
        for(int j=20000; j>=b[i]-1; j--){
            f[j]=min(f[j], f[j-(b[i]-1)]+c[i]);
        }
    }
    for(int i=k; i<=20000; i++) ans=min(ans, f[i]);

    memset(f, 0x3f, sizeof(f));f[0]=0;
    for(int i=1; i<=n; i++){
        for(int j=20000; j>=a[i]+b[i]; j--){
            f[j]=min(f[j], f[j-(a[i]+b[i])]+c[i]);
        }
    }
    for(int i=2*k-1; i<=20000; i++){
        ans=min(ans, f[i]);
    }
    if(ans==f[20001]){
        printf("-1\n");
    }
    else{
        printf("%d\n", ans);
    }

	return 0;
}
posted @   liweihang  阅读(161)  评论(0编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
Live2D
欢迎阅读『DP刷题记录』
点击右上角即可分享
微信分享提示