DP训练笔记

预计时间一个月,一天的量为1-2道左右,难度不均,黄-紫都有,后阶段紫

// https://www.luogu.com.cn/problem/P4141

// 对于任何一个物品对答案的贡献都是从1到n递推过去的,所以
// 只需要开一个相同的数组然后从头遍历一遍,把该物品对答案的贡献减去就可以了

#include<bits/stdc++.h>
using namespace std;
const int N=4020;
int dp[N],res[N],w[N],n,m;
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>w[i];
    dp[0]=1,res[0]=1;
    for(int i=1;i<=n;i++)
        for(int j=m;j>=w[i];j--) dp[j]=(dp[j]+dp[j-w[i]])%10;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(j<w[i]) res[j]=dp[j];
            //对于每个包含于i的物品答案数,用dp数-去未包含他的数即为答案数
            else res[j]=(dp[j]-res[j-w[i]]+10)%10;//别忘了相减取模要+模数
            cout<<res[j];
        }
        cout<<endl;
    }
    return 0;
}
//https://www.luogu.com.cn/problem/CF607B

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=2040,mod=1e9+7;
int n,t,dp[N][N],a[N];
signed main()
{
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=n;i>=1;i--){ //区间dp,这样的顺序是对的
        for(int j=i;j<=n;j++){
            //初始化:
            if(i==j) {dp[i][j]=1;continue;}
            if(i+1==j) {dp[i][j]=(a[i]==a[j]?1:2);continue;}
            dp[i][j]=1e18;
            //枚举区间长度
            for(int k=i;k<j;k++) 
                dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
            if(a[i]==a[j]) dp[i][j]=min(dp[i][j],dp[i+1][j-1]);
        }
    }
    cout<<dp[1][n];
    return 0;
}
//https://www.luogu.com.cn/problem/CF1513C

//可以肯定的是,对于任何一位的数来说,他能变得长度是确定得
//例如 9,它经过2两次变化长度一定是2,其它位对9这一位没影响
//所以先预处理来每个数能够变成得最多长度

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+10,mod=1e9+7;
string s;
int n,t,a[N],f[N],res,num,ans,m,k;
vector<vector<int>>dp(N+1,vector<int>(10));
void solve()
{
    cin>>s>>n; res=0;
    for(char c:s) res=(res+dp[n][c-'0'])%mod;
    cout<<res<<endl;
}
signed main()
{
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>t;
    //dp[i][j]为,数字j经过i次变换之后有多长
    //状态转移为 : 9先由1位变成2位,然后下一次变化后,8就是9,7就是8,以此类推
    for(int i=0;i<=9;i++) dp[0][i]=1;
    for(int i=1;i<=N;i++){
        for(int j=0;j<=8;j++) dp[i][j]=dp[i-1][j+1]%mod;
        dp[i][9]=(dp[i-1][1]+dp[i-1][0])%mod;
       // for(int j=0;j<=9;j++) cout<<dp[i][j]<<' ';cout<<endl;
    }
    while(t--){
        solve();
    }
    return 0;
}

// //另一种思路: 逢10进1,就有:
// if(i+j>=10) dp[i][j]=dp[i+j-10][0]+dp[i+j-10][1]%mod;
// else dp[i][j]=1;
// 如果i+j>10,那么操作后是两位,分别是1和0,而从j到10需要10-j步,还剩i+j-10步
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+10,mod=1e9+7;
string s;
int n,t,a[N],f[N],res,num,ans,m,k;
vector<vector<int>>dp(N+1,vector<int>(10));
void solve()
{
    cin>>s>>n; res=0;
    for(char c:s) res=(res+dp[n][c-'0'])%mod;
    cout<<res<<endl;
}
signed main()
{
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>t;
    //dp[i][j]为,数字j经过i次变换之后有多长
    //状态转移为 : 9先由1位变成2位,然后下一次变化后,8就是9,7就是8,以此类推
    for(int i=0;i<=9;i++) dp[0][i]=1;
    for(int i=1;i<=N;i++){
        for(int j=0;j<=9;j++)
            if(i+j>=10) dp[i][j]=dp[i+j-10][0]+dp[i+j-10][1]%mod;
            else dp[i][j]=1;
       // for(int j=0;j<=9;j++) cout<<dp[i][j]<<' ';cout<<endl;
    }
    while(t--){
        solve();
    }
    return 0;
}
//https://www.luogu.com.cn/problem/CF711C
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=205;
int dp[N][N][N],res=1e18,n,m,x;
/*
dp[i][j][k] 是第i个树,第j个颜色,当前是第k组
col[i]是当前的染色情况
c是 n x m 的花费
*/
int col[N],c[N][N];
signed main()
{
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>n>>m>>x;
    for(int i=1;i<=n;i++) cin>>col[i];
    for(int i=1;i<=n;i++) 
        for(int j=1;j<=n;j++) cin>>c[i][j];
    //初始化
    memset(dp,0x3f,sizeof dp);
    //如果说第一位已经被染了,那么就不管了,因为要从第一位开始递推,如果没染那就都染一遍
    if(col[1]) dp[1][col[1]][1]=0;
    else for(int i=1;i<=m;i++) dp[1][i][1]=c[1][i];
    for(int i=2;i<=n;i++){
        if(col[i]){ //如果已经确定被染色
            for(int j=1;j<=m;j++){
                for(int k=1;k<=x;k++){
                    if(j!=col[i]) dp[i][col[i]][k]=min(dp[i][col[i]][k],dp[i-1][j][k-1]); //不同组
                    else dp[i][col[i]][k]=min(dp[i][col[i]][k],dp[i-1][col[i]][k]);// 同组
                }
            }
        }
        else{
            for(int j=1;j<=m;j++){
                for(int h=1;h<=m;h++){
                    for(int k=1;k<=x;k++){
                        if(j!=h) dp[i][j][k]=min(dp[i][j][k],dp[i-1][h][k-1]+c[i][j]);
                        else dp[i][j][k]=min(dp[i][j][k],dp[i-1][j][k]+c[i][j]);
                    }
                }
            }
        }
    }
    if(col[n]) res=dp[n][col[n]][x];
    else for(int i=1;i<=m;i++) res=min(res,dp[n][i][x]);
    cout<<(res>=1e18/2?-1:res);
    return 0;
}

 

//https://codeforces.com/contest/706/problem/C

//考虑dp,类似于染色树,把情况考虑全即可
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10,inf=0x7fffffffffff;
string s[N],rev_s[N];
int n,res,dp[N][2],c[N];
signed main()
{
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++) cin>>c[i];
    for(int i=1;i<=n;i++){
        dp[i][1]=dp[i][0]=inf;
        cin>>s[i];rev_s[i]=s[i];
        reverse(rev_s[i].begin(),rev_s[i].end());
    }
    dp[1][0]=0,dp[1][1]=c[1];
    for(int i=2;i<=n;i++){
        if(s[i]>=s[i-1]) dp[i][0]=dp[i-1][0];
        if(rev_s[i]>=rev_s[i-1]) dp[i][1]=dp[i-1][1]+c[i];
        if(rev_s[i]>=s[i-1]) dp[i][1]=min(dp[i][1],dp[i-1][0]+c[i]);
        if(s[i]>=rev_s[i-1]) dp[i][0]=min(dp[i][0],dp[i-1][1]);
    } 
    if(min(dp[n][0],dp[n][1])==inf) cout<<"-1";
    else cout<<min(dp[n][0],dp[n][1]); 
}

 

//https://www.luogu.com.cn/problem/P2758
#include<bits/stdc++.h>
using namespace std;
const int N=2050;
string st=" ",ed=" ",s,ss;
int dp[N][N],res,lst,n,m;
//第一维存A串,二维存B串,意思是从A到B需要用的次数

/*
1.确定子问题:

由于对于字符串的操作只有4种情况(删除,添加、更改、不变)
所以该题的子问题就是进行了这4种操作后的A字符串变为B字符串需要多少步。

2.定义状态:

也就是说递归的dp函数需要哪些参数,参数越少越好因为需要建memo。
后来想到dp(i,j)代表字符串A的前i个字符(包括第i个)变为字符串B的前j个(包括第j个)需要多少步。
也就是说解出来dp(lenA,lenB)就可以了。
*/
int main()
{
    cin>>s>>ss;
    st+=s,ed+=ss;
    n=st.size(),m=ed.size();
    for(int i=1;i<=n;i++) dp[i][0]=i;
    for(int j=1;j<=m;j++) dp[0][j]=j;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            /*
            对于转移方程: 对于减操作: 可以看作是删除当前A的第i位,变成 (i-1,j),然后操作次数+1;
                                    通俗的说就是i位置如果选择减,那么就会变成(i-1,j)的状态,然后操作次数+1;
                         对于加操作: 同理可以看看作,抵消掉B的第j位,我们加的字符一定是与B对应位相等的,所以会变成
                                    (i,j-1)的状态
                         对于变化操作: 也就是我现在这两位相等了,那我可以直接把这两位删除,如果本来就相等那就不变
            */
           dp[i][j]=min({dp[i-1][j]+1,dp[i][j-1]+1,dp[i-1][j-1]+(st[i]==ed[j]?0:1)});
    cout<<dp[n][m];
    return 0;   
}

 

//https://www.luogu.com.cn/problem/P4933

//遍历前面i之前的所有状态,然后res用于计数
//O(n2)
//f[i][j]为第i位状态,公差为j
#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int N=40040,mod=998244353,p=20000;
int n,h[N],dp[1010][N],mx,res;
signed main()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>h[i];
    for(int i=1;i<=n;i++){
        res++;//他自己算一个
        for(int j=1;j<i;j++){
            dp[i][h[i]-h[j]+p]+=dp[j][h[i]-h[j]+p]+1;//等差为h[i]-h[j]的状态+1
            dp[i][h[i]-h[j]+p]%=mod;
            res=(res+dp[j][h[i]-h[j]+p]+1)%mod;//把i之前的所有状态都加起来,因为状态更新了
        }
    }
    cout<<res%mod;
    return 0;
}

//O(nv)

#include<bits/stdc++.h>
using namespace std;
const int N=80010,mod=998244353,p=20000;
int n,h[200001],dp[1010][N],res,v,v_;
vector<int>pos[200001];
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>h[i],pos[h[i]].push_back(i);
    for(int i=1;i<=n;i++)
        for(int j=-p;j<=p;j++) dp[i][j+40000]=1;
    for(int i=1;i<=n;i++){
        v_=max(v_,h[i]);
        for(int j=-v_;j<=h[i];j++){//枚举公差
            for(int k=0;k<pos[h[i]-j].size();k++){
                int x=pos[h[i]-j][k];
                if(x>=i) break;//小小优化,假如当前下标大于等于i,则后面的下标也一定大于i,因此直接退出即可
                dp[i][j+40000]+=dp[x][j+40000],dp[i][j+40000]%=mod;
                res+=dp[x][j+40000],res%=mod;
            }
        }
    }
    cout<<(res+n)%mod;
    return 0;
}

 

//https://www.luogu.com.cn/problem/P8625
//树上dp,求连通块的最大值 
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+10;
int dp[N],n,v[N],res,ans;
int e[N],ne[N],h[N],idx;
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int dfs(int u,int fa)
{
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        if(j!=fa) dp[u]=max(dp[u],dp[u]+dfs(j,u));
    }
    return dp[u];
}
signed main()
{
    cin>>n;
    memset(h,-1,sizeof h); 
    for(int i=1;i<=n;i++) cin>>v[i],dp[i]=v[i];
    for(int i=1;i<n;i++){
        int u,v; cin>>u>>v;
        add(u,v),add(v,u);
    }
    res=dfs(1,0);
    for(int i=1;i<=n;i++) ans=max(dp[i],ans);
    cout<<ans;
    return 0;
}
//https://www.luogu.com.cn/problem/P2733

//矩阵dp 
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=300;
int dx[]={1,0,1},dy[]={0,1,1};
int n,dp[N][N],res,col[N];
signed main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++) scanf("%1lld",&dp[i][j]);
    for(int k=2;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                if(dp[i+1][j]+dp[i+1][j+1]+dp[i][j+1]+dp[i][j]==(k-1)*4)
                    dp[i][j]++,col[k]++;

    for(int i=2;i<=n;i++) 
        if(col[i]) cout<<i<<' '<<col[i]<<endl;
    return 0;
}

//矩阵前缀和 
#include<bits/stdc++.h>
using namespace std;
const int N=300;
int dp[N][N],mp[N][N],res,n;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++) scanf("%1d",&mp[i][j]);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            dp[i][j]=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]+mp[i][j];
    for(int k=2;k<=n;k++){
        res=0;
        for(int i=k;i<=n;i++){
            for(int j=k;j<=n;j++){
                if(dp[i][j]-dp[i-k][j]-dp[i][j-k]+dp[i-k][j-k]==k*k) res++;
            }
        }
        if(res==0) return 0;
        cout<<k<<' '<<res<<endl;
    }
    return 0;
}

 

//https://www.luogu.com.cn/problem/P1004

//首先提供二维dp,二维dp的思路为ij表示i行j列时的可以取得最大值
//类似于贪心,先进行第一遍循环,取到最优,然后把第一遍取的数全变为0,再进行第二遍的取
//但是这种方法并不一定是全局的最优解

//0    0    2    3    0    0    0
//0    0    3    0    0    0    0
//0    0    3    0    0    0    0
//0    0    0    0    0    0    0
//0    0    0    0    4    0    0
//0    0    0    0    4    0    0
//0    0    0    0    4    0    0
//如图,走第一遍可得出终点时最大值为20,去掉已经走过的点后图如下:                        
//0    0    0    3    0    0    0
//0    0    0    0    0    0    0
//0    0    0    0    0    0    0
//0    0    0    0    0    0    0
//0    0    0    0    0    0    0
//0    0    0    0    0    0    0
//0    0    2    0    0    0    0 
//然后会发现我们无法全部走完,也正符合贪心策略,“只注重眼前的利益”,因此此题使用二维dp绝非正解,上代码:
#include<bits/stdc++.h>
using namespace std;
const int N=10;
int dx[]={0,1},dy[]={1,0},n,mp[N][N];
int dp[N][N],res,ans;
int main()
{
    cin>>n;
    int a,b,c;
    while(cin>>a>>b>>c&&a+b+c>0) mp[a][b]=c;
//    for(int i=1;i<=n;i++){
//        for(int j=1;j<=n;j++) cout<<mp[i][j]<<' ';
//        cout<<endl;
//    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            dp[i][j]=max(dp[i-1][j]+mp[i][j],dp[i][j-1]+mp[i][j]);
    res=dp[n][n],ans=dp[n][n];
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(dp[i][j]==res&&dp[i][j]!=0) res-=mp[i][j],mp[i][j]=0,i=0,j=0;
        }
    }
    memset(dp,0,sizeof dp);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            dp[i][j]=max(dp[i-1][j]+mp[i][j],dp[i][j-1]+mp[i][j]);
    cout<<ans+dp[n][n];
    return 0;
}

//正确思路:  
//四维dp,用ijkl表示思维,ij表示第一遍走的,kl表示第二遍走的,然后进行n^4的dp
//如果遇到相同的点就直接减去 
#include<bits/stdc++.h>
using namespace std;
const int N=10;
int n,m,dp[N][N][N][N],mp[N][N];
int main()
{
    cin>>n;
    int a,b,c;
    while(cin>>a>>b>>c&&a+b+c>0) mp[a][b]=c;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            for(int k=1;k<=n;k++)
                for(int l=1;l<=n;l++){
                    dp[i][j][k][l]=max(max(dp[i-1][j][k-1][l],max(dp[i-1][j][k][l-1],dp[i][j-1][k][l-1])),dp[i][j-1][k-1][l])+mp[i][j]+mp[k][l];
                    if(i==k&&j==l) dp[i][j][k][l]-=mp[k][l];
                }
    cout<<dp[n][n][n][n];
    return 0;
}

 

posted @ 2023-10-26 14:51  o-Sakurajimamai-o  阅读(28)  评论(0)    收藏  举报
-- --