dp题

牛客 施魔法

在这里插入图片描述

思路:

在这里插入图片描述

code:
//https://ac.nowcoder.com/acm/contest/3003/H
#include<bits/stdc++.h>
using namespace std;
const int maxm=3e5+5;
int a[maxm];
int d[maxm];//d[i]表示用掉去前i个元素的最小代价
//d[i]=min{d[j-1]+a[i]-a[j]}=min{d[j-1]-a[j]}+a[i] 1<=j<=(i-k+1)
signed main(){
    int n,k;
    cin>>n>>k;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    sort(a+1,a+1+n);
    for(int i=1;i<k;i++)d[i]=2e9;
    int pre=0-a[1];
    for(int i=k;i<=n;i++){
        d[i]=pre+a[i];
        pre=min(pre,d[i-k+1]-a[i-k+2]);
    }
    cout<<d[n]<<endl;
    return 0;
}

牛客 牛牛的宝可梦Go

题面:

在这里插入图片描述

思路:

在这里插入图片描述

code:
//https://ac.nowcoder.com/acm/contest/3004/J
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=1e3+5;
int g[maxm][maxm];
int d[maxm*100];
struct Node{
    int t,p,v;
}e[maxm*100];
bool cmp(Node a,Node b){
    return a.t<b.t;
}
signed main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++)g[i][j]=(i==j?0:1e9);
    }
    for(int i=1;i<=m;i++){
        int a,b;
        cin>>a>>b;
        g[a][b]=g[b][a]=1;
    }
    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
            }
        }
    }
    int k;
    cin>>k;
    for(int i=1;i<=k;i++){
        cin>>e[i].t>>e[i].p>>e[i].v;
    }
    e[0].p=1;
    sort(e+1,e+1+k,cmp);
    int premax=0;
    int ans=0;
    for(int i=1;i<=k;i++){
        if(i>200){
            premax=max(premax,d[i-200]);
            d[i]=premax+e[i].v;
        }else{
            d[i]=-1e18;//不能初始化为0,否则之后的数可能会利用这个数转移
        }
        for(int j=1;j<=200&&i-j>=0;j++){
            if(g[e[i-j].p][e[i].p]<=e[i].t-e[i-j].t){//如果时间差内可以到达则可以转移
                d[i]=max(d[i],d[i-j]+e[i].v);
            }
        }
        ans=max(ans,d[i]);
    }
    cout<<ans<<endl;
    return 0;
}

Acwing 272. 最长公共上升子序列

题意:

给长度为n的两个数组a和b
问最长公共上升子序列的长度是多少。

最长公共上升子序列定义为:既是a的一个上升子序列,也是b的一个上升子序列

n<=3e3

思路:
f[i][j]表示前i个a[i]中,以b[j]为结尾的最大公共上升子序列
则对于一个固定的i和j,f[i][j]的值有两种情况:
1.不含a[i],则f[i][j]=f[i-1][j]
2.包含a[i],那么a[i]是结尾,又因为b[j]是结尾,因此需要先满足a[i]=b[j]
这时候f[i][j]=max(f[i-1][k])+1,(k<j,且a[i]>b[k])

枚举i一层循环,枚举j一层循环,枚举k一层循环,复杂度O(n^3),显然需要优化
f[i-1][k]用一个变量存起来,不断维护就可以优化掉枚举k了
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=3e3+5;
int a[maxm],b[maxm];
int f[maxm][maxm];
//f[i][j]表示前i个a[i]中以b[j]为结尾的最大长度
signed main(){
    int n=re;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    for(int i=1;i<=n;i++){
        cin>>b[i];
    }
    for(int i=1;i<=n;i++){
        int ma=0;//记录f[i-1][k]中最大的
        for(int j=1;j<=n;j++){
            f[i][j]=f[i-1][j];//不含a[i]
            if(a[i]==b[j]){//包含a[i],则结尾为a[i],又因为结尾为b[j],则要满足a[i]=b[j]
                f[i][j]=max(f[i][j],ma+1);//f[i-1][k]中最大的+1,(k<j)
            }
            if(a[i]>b[j]){//更新ma
                ma=max(ma,f[i-1][j]);
            }
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++){
        ans=max(ans,f[n][i]);
    }
    cout<<ans<<endl;
    return 0;
}

CodeForces 1096 D. Easy Problem

题意:

给定一个长度为n的字符串s,只由字符hard组成
删除s(i)的代价为a(i)
问使得串中不存在子序列hard的最小代价

思路:

开始想的是删除某一种字符就行了,这样是不对的
反例:
5
harard
100 1 100 100 1 100

正解:
因为子序列是存在顺序的问题,所以考虑维护使其不存在每个前缀的最小代价
1代表h
2代表ha
3代表har
4代表hard
d(i,sta)表示前i个字符中令其无法构成子序列sta的最小代价
对于无法构成前i个前缀的最小花费,先考虑无法构成前i-1个字符的最小花费 然后再考虑将这个位置的字符删除

ps:
由于只需要前一个位置的dp值
所以还可以顺便滚动数组优化

别人这题似乎被归到字符串dp了(原来dp还有单独出字符串一类?!)

code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=2e5+5;
char s[maxm];
int a[maxm];
int d[5];//滚动数组
signed main(){
    int n;
    cin>>n;
    scanf("%s",s+1);
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    for(int i=1;i<=n;i++){
        if(s[i]=='h'){
            d[1]=d[1]+a[i];
        }else if(s[i]=='a'){
            d[2]=min(d[1],d[2]+a[i]);
        }else if(s[i]=='r'){
            d[3]=min(d[2],d[3]+a[i]);
        }else if(s[i]=='d'){
            d[4]=min(d[3],d[4]+a[i]);
        }
    }
    cout<<d[4]<<endl;
    return 0;
}

CodeForces687 C. The Values You Can Make

题意:

给定n枚银币,每个硬币有价值c(i),现在给定一个k,保证n枚硬币一定能构成k
问构成k的这些硬币还能构成多少其他的数,输出这些数。

例如用1,4,5,5构成10,
一种方案是1,4,5,其中1,4,5还可以构成0,1,4,5,6,9,10
一种方案是5,5,其中5,5还可以构成0,5,10
答案就是他们的并集0,1,4,5,6,9,10

思路:

d(i,j)表示凑成i元的时候是否能用凑成i元的硬币构成j元,如果可以,则d(i,j)=1,否则为0,显然j<=i
对于新加入的硬币x,如果d(i,j)=1,那么d(i+x,j)=1,d(i+x,j+x)=1,转移方程就出来了

因此对于每一枚硬币x,枚举i 和 j,进行转移即可。
因为d(i,j)会更新d(i+x,j)和d(i+x,j+x),为了保证无后效性,i和j都需要逆序枚举。
最后统计满足d(k,i)=1的i有多少个即可。

初始情况下d(0,0)=1

code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=505;
int d[maxm][maxm];
int n,k;
signed main(){
    cin>>n>>k;
    d[0][0]=1;
    while(n--){
        int x;
        cin>>x;
        for(int i=k;i>=0;i--){//逆序
            if(i+x<=k){
                for(int j=i;j>=0;j--){//逆序
                    d[i+x][j]|=d[i][j];
                    d[i+x][j+x]|=d[i][j];
                }
            }
        }
    }
    vector<int>ans;
    for(int i=0;i<=k;i++){
        if(d[k][i]){
            ans.push_back(i);
        }
    }
    cout<<ans.size()<<endl;
    for(int v:ans){
        cout<<v<<' ';
    }
    return 0;
}

CodeForces1101 D. GCD Counting

题意:

给一颗n个顶点的树,每个点有点权a(i),现在你要找出树上最长的路径,满足路径上的点权gcd不为1
输出这条路径的长度,路径的长度定义为路径上点的数量。
数据范围:n<=2e5,a(i)<=2e5

思路:

题目说gcd不为1,那么gcd只要是某个质因子就行。
发现2、3、5、7、9、11、13这7个质数的乘积以及大于2e5,因此每个数的质因子个数不超过7个。
对于树上每个点,只需要记录以这个点的每个质因子为gcd的最长向下扩展长度。
令d(i,j)表示编号为i的点,第j个质因子为gcd的最长扩展长度。

流程:
假设当前节点为x,遍历x的子节点,设子节点为v,
遍历x的质因子,假设第i个质因子为t,如果a(v)%t==0,说明v中也有质因子t,那么x可以向v扩展,
找到t在v的质因子的位置pos,(位置可以提前用map标记)
则d(v,pos)可能可以更新d(x,i),因为题目要求最长,所以d(x,i)要用最大的d(v,pos)更新,
记录位置i所能扩展的最大值和次大值(次大值用于计算答案,下面有说)

因为题目要求最大值,考虑把每个节点作为连接点,
以x为连接点的答案为1.x可扩展的最大值,2.x可扩展的次大值,3.因为还要算上x,因此+1

感觉看代码更好理解

code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=2e5+5;
vector<int>g[maxm];
vector<int>fac[maxm];
map<int,int>mark[maxm];//mark(x,t)=pos表示t是x的第pos个质因子
int d[maxm][10];
int a[maxm];
int ans;
int n;
void dfs(int x,int fa){
    for(int i=0;i<(int)fac[x].size();i++){
        d[x][i]=1;
    }
    int ma[10]={0};//最大
    int se[10]={0};//次大
    for(int v:g[x]){
        if(v==fa)continue;
        dfs(v,x);
        for(int i=0;i<(int)fac[x].size();i++){
            int t=fac[x][i];
            if(a[v]%t==0){
                int pos=mark[v][t];
                int x=d[v][pos];
                if(x>ma[i]){
                    se[i]=ma[i];
                    ma[i]=x;
                }else if(x>se[i]){
                    se[i]=x;
                }
            }
        }
    }
    for(int i=0;i<fac[x].size();i++){
        d[x][i]+=ma[i];
        ans=max(ans,ma[i]+se[i]+1);
    }
}
signed main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        int x=a[i];
        for(int j=2;j*j<=x;j++){
            if(x%j==0){
                fac[i].push_back(j);
                while(x%j==0)x/=j;
            }
        }
        if(x!=1)fac[i].push_back(x);
        for(int j=0;j<(int)fac[i].size();j++){
            mark[i][fac[i][j]]=j;//标记一下质因子的位置
        }
    }
    for(int i=1;i<n;i++){
        int a,b;
        cin>>a>>b;
        g[a].push_back(b);
        g[b].push_back(a);
    }
    dfs(1,-1);
    cout<<ans<<endl;
    return 0;
}

牛客 操作集锦

题意:

给一个长度为n的字符串,和一个整数k,问有多少个长度为k的本质不同的子序列
答案模1e9+7

思路:

在这里插入图片描述

code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=1e3+5;
const int mod=1e9+7;
int d[maxm][maxm];//d[i][j]为前i个字符组成长度为j的子序列个数
int pre[maxm];
char s[maxm];
int n,k;
signed main(){
    cin>>n>>k;
    scanf("%s",s+1);
    for(int i=0;i<=n;i++){//边界
        d[i][0]=1;
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=k&&j<=i;j++){
            d[i][j]+=d[i-1][j-1];//以i为结尾
            if(j<=i-1){//不以i为结尾
                d[i][j]+=d[i-1][j];
            }
            if(pre[s[i]-'a']&&j-1<=pre[s[i]-'a']-1){
                d[i][j]-=d[pre[s[i]-'a']-1][j-1];
            }
            d[i][j]=(d[i][j]+mod)%mod;
        }
        pre[s[i]-'a']=i;
    }
    cout<<d[n][k]<<endl;
    return 0;
}
//https://ac.nowcoder.com/acm/contest/4853/C

CodeForces269 B. Greenhouse Effect

题意:

有n株植物共m种,每株植物有种类和所在的坐标(坐标为实数),
现在要求将某些植物移动,使得相同种类的植物放在同一组(相邻),
并且第i种放在左边起第i组。

解法:

这题的坐标是没用的

最后的结果,植物的种类一定是非递减的
考虑有多少个植物最后不会移动,那么答案就是n-不移动的植物数量
不移动的植物数量为初始情况下植物种类的最长非递减序列,dp一下就行了

code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=5e3+5;
int d[maxm];
int a[maxm];
signed main(){
    int n,m;cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        double temp;cin>>temp;//坐标没用
    }
    int ans=0;
    for(int i=1;i<=n;i++){
        d[i]=1;
        for(int j=1;j<i;j++){
            if(a[i]>=a[j]){
                d[i]=max(d[i],d[j]+1);
            }
        }
        ans=max(ans,d[i]);
    }
    cout<<n-ans<<endl;
    return 0;
}

hdu1421 搬寝室

题意:

由n个行李箱,需要从中拿出2k个行李箱,分k次拿,每次左手一个右手一个
假设某一次拿的行李箱的质量为a和b,那么这次的疲劳度就是(a-b)2
问k次的最小疲劳和是多少

数据范围:2k<=n<=2000

解法:

显然拿的行李箱的质量a和b的差值越小越好,因此先对行李箱按质量排序
定义d(i,j)为前i个行李箱取出j对行李项的最小疲劳值

如果某个物品i被取了,一定是和i-1匹配。
对于当前物品i,如果取则d(i,j)=d(i-2,j-1)+(a(i)-a(i-1)2
如果不取则d(i,j)=d(i-1,j)

复杂度O(n2)

code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=2e3+5;
int d[maxm][maxm];
int a[maxm];
int n,k;
signed main(){
    while(cin>>n>>k){
        for(int i=1;i<=n;i++)cin>>a[i];
        sort(a+1,a+1+n);
        for(int i=1;i<=n;i++)for(int j=1;j<=k;j++)d[i][j]=1e18;
        //d[i][0]=0,不要初始化
        for(int i=2;i<=n;i++){
            for(int j=1;j<=k&&j*2<=i;j++){
                d[i][j]=d[i-1][j];
                d[i][j]=min(d[i][j],d[i-2][j-1]+(a[i]-a[i-1])*(a[i]-a[i-1]));
            }
        }
        cout<<d[n][k]<<endl;
    }
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值