ACW各种dp模板题 day29

背包的就不贴上来了 感觉没啥好说的

AcWing 898. 数字三角形

线性dp 第一题 应该都是一眼秒吧 感觉自己第一遍学的时候是真的菜 呃呃

我们注意边界情况就可以了 状态转移没啥好说的 下一道

#include <bits/stdc++.h>
using namespace std;
const int N = 510;
const int M = 1e4+10;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int max(int x,int y){return x>y?x:y;}
int min(int x,int y){return x<y?x:y;}
int a[N][N],f[N][N];
signed main(){
    fast
    int n;cin>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=i;j++)
            cin>>a[i][j];
    for(int i=1;i<=n;i++){
        for(int j=1;j<=i;j++){
            if(j==1)f[i][j]=f[i-1][j]+a[i][j];
            else if(j==i)f[i][j]=f[i-1][j-1]+a[i][j];
            else f[i][j]=max(f[i-1][j-1],f[i-1][j])+a[i][j];
        }
    }
    int ans=-2e9;
    for(int i=1;i<=n;i++)ans=max(ans,f[n][i]);
    cout<<ans<<endl;
    return 0^0;
}

895. 最长上升子序列

其实这道题也差不多能一眼秒的 直接暴力n^2就过了

状态表示:f[i]表示为末尾为第i位的max

然后我们就可以枚举每个i前面的数字

if (w[j] < w[i])f[i] = max(f[i], f[j] + 1);

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3+10;
const int M = 1e4+10;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int max(int x,int y){return x>y?x:y;}
int min(int x,int y){return x<y?x:y;}
int n,f[N],w[N];
signed main(){
    fast
    cin>>n;
    for(int i=1;i<=n;i++)cin>>w[i];
    for(int i=0;i<N;i++)f[i]=1;
    int ans=-2e9;
    for(int i=1;i<=n;i++) {
        for (int j = 1; j <= i; j++) {
            if (w[j] < w[i])f[i] = max(f[i], f[j] + 1);
            ans=max(ans,f[i]);
        }
    }
    cout<<ans<<endl;
    return 0^0;
}

896. 最长上升子序列 II

到这里才感觉有点难度 可是我还是记得到 做法是二分

最开始没想清楚在哪二分 然后去洗了个澡就想出来了 (这其实也告诉我们一个道理 一道题不能死磕 说不定过几天 就能想出来了

这次我们状态表示就不再是simple版那样了

我们把f[i]表示为当子序列为i位时最小的序号是谁 我们不难看出这个其实有单调性啊

要是我们后面的数字序号很小 那么就可以和前面的替换之 要是很大 那我们就可以在后面补上

反正都是有地方可以去的

代码甚至比simple 还短 

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
const int M = 1e4+10;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int max(int x,int y){return x>y?x:y;}
int min(int x,int y){return x<y?x:y;}
int n,w[N];
signed main(){
    fast
    cin>>n;
    for(int i=1;i<=n;i++)cin>>w[i];
    vector<int>f;
    for(int i=1;i<=n;i++){
        if(lower_bound(f.begin(),f.end(),w[i])==f.end())f.push_back(w[i]);
        else f[lower_bound(f.begin(),f.end(),w[i])-f.begin()]=w[i];
    }
    cout<<f.size()<<endl;
    return 0^0;
}

897. 最长公共子序列

我们不难想到把f_i_j 表示为 a_i 和 b_j 能凑出的子串max

状态方程 f[i][j]=max(f[i-1][j],f[i][j-1]); 当然要是a_i==b_j?f[i][j]=max(f[i][j],f[i-1][j-1]):max(f[i][j],f[i-1][j-1]+1)

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3+10;
const int M = 1e4+10;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int max(int x,int y){return x>y?x:y;}
int min(int x,int y){return x<y?x:y;}
int f[N][N];
signed main(){
    fast
    int n,m;cin>>n>>m;
    char a[N],b[N];
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=m;i++)cin>>b[i];
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(a[i]==b[j])f[i][j]=max(f[i-1][j-1]+1,max(f[i-1][j],f[i][j-1]));
            else f[i][j]=max(f[i-1][j],f[i][j-1]);
        }
    }
    cout<<f[n][m]<<endl;
    return 0^0;
}

AcWing 902. 最短编辑距离

这个很清楚的二维表示 状态方程的三种情况也写得很明白

f_i_j 表示 a_i 变成 b_j 的min

状态计算 :从最后一步考虑

要是我们f_i_j是要进行一步删除操作才能匹配 自然 f_i-1_j  +1 (这个有点绕 可能会整反

增加自然就是 f_i_j-1 +1

改的话自然两种情况 a[i]==b[j]?f[i][j]=min(f[i][j],f[i-1][j-1]):f[i][j]=min(f[i][j],f[i-1][j-1]+1);

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3+10;
const int M = 1e4+10;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int max(int x,int y){return x>y?x:y;}
int min(int x,int y){return x<y?x:y;}
int f[N][N];
signed main(){
    fast
    int n,m;
    char a[N];cin>>n>>a+1;
    char b[N];cin>>m>>b+1;
    for(int i=1;i<=n;i++)f[i][0]=i;
    for(int i=1;i<=m;i++)f[0][i]=i;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1);
            a[i]==b[j]?f[i][j]=min(f[i][j],f[i-1][j-1]):f[i][j]=min(f[i][j],f[i-1][j-1]+1);
        }
    }
    cout<<f[n][m];
    return 0^0;
}

AcWing 899. 编辑距离

和刚刚一模一样 只是处理了一下输入

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3+10;
const int M = 1e4+10;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int max(int x,int y){return x>y?x:y;}
int min(int x,int y){return x<y?x:y;}
char s[N][15];
int f[N][N];
signed main(){
    int n,m;
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
    while(m--){
        char a[N];int x,ans=0;scanf("%s%lld",a+1,&x);
        for(int k=1;k<=n;k++) {
            int cnt1=strlen(a+1),cnt2= strlen(s[k]+1);
            for(int i=1;i<=cnt1;i++)f[i][0]=i;
            for(int i=1;i<=cnt2;i++)f[0][i]=i;
            for (int i = 1; i<=cnt1; i++) {
                for (int j = 1;j<=cnt2; j++) {
                    f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1);
                    if(a[i]==s[k][j])f[i][j]=min(f[i][j],f[i-1][j-1]);
                    else f[i][j]=min(f[i][j],f[i-1][j-1]+1);
                }
            }
            if(x>=f[cnt1][cnt2])ans++;
        }
        cout<<ans<<endl;
    }
    return 0^0;
}

AcWing 282. 石子合并

区间dp

有点分治的意思?所以我们这题用了记忆化搜索来做了一下

当然也可以倒着推 也可以枚举长度

状态表示:f_i_j 表示 [i,j] 区间构成的min

状态方程:f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]) 后面就是前缀和

dp版本 运行时间: 102 ms

#include <bits/stdc++.h>
using namespace std;
const int N = 310;
int f[N][N],s[N];
signed main(){
    int n;cin>>n;
    for(int i=1;i<=n;i++)cin>>s[i],s[i]+=s[i-1];
    memset(f,0x3f,sizeof f);
    for(int i=n;i>=1;i--)
        for(int j=i;j<=n;j++)
            for(int k=i;k<=j;k++)
                i==j?f[i][j]=0:f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]);
    cout<<f[1][n]<<endl;
    return 0^0;
}

记忆化搜索版本 运行时间: 151 ms

#include <bits/stdc++.h>
using namespace std;
const int N = 310;
const int M = 1e4+10;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int max(int x,int y){return x>y?x:y;}
int min(int x,int y){return x<y?x:y;}
int a[N],s[N],f[N][N];
int dp(int l,int r){
    int &v=f[l][r];
    if(v!=-1)return v;
    v=1e9;
    for(int k=l;k<=r;k++)l==r?f[l][r]=0:f[l][r]=min(f[l][r],dp(l,k)+dp(k+1,r)+s[r]-s[l-1]);
    return v;
}
signed main(){
    fast
    int n;cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i],s[i]=s[i-1]+a[i];
    memset(f,-1,sizeof f);
    cout<<dp(1,n);
    return 0^0;
}

AcWing 900. 整数划分

计数类dp

我们可以将n拆成有n个权重的物品 我们要装满体积为n的背包 可以用无数次 求cnt

状态表示:f_i_j 前i种物品装满体积为j的背包的cnt

状态计算:f[i][j]=f[i-1][j]+f[i-1][j-i]+f[i-1][j-2i]+......

后面可以用f[i][j-i]来表示 完全背包推过

所以状态方程就是:f[i][j]=(f[i-1][j]+f[i][j-i])

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3+10;
const int M = 1e4+10;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int max(int x,int y){return x>y?x:y;}
int min(int x,int y){return x<y?x:y;}
int f[N][N];
signed main(){
    fast
    int n;cin>>n;
    for(int i=1;i<=n;i++)f[i][0]=1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            f[i][j]=f[i-1][j];
            if(j>=i)f[i][j]=(f[i-1][j]+f[i][j-i])%mod;
        }
    }
    cout<<f[n][n]<<endl;
    return 0^0;
}

AcWing 291. 蒙德里安的梦想

第一次学的dp的时候跳了状态压缩

最近看群友用这个秒天秒地 不仅心动起来 才发现这个其实也没那么难

但是使用数据是真的小啊

首先我们有一个推论:

这道题我们合法的横向小方块摆放的位置的方案数就是总的方案数 

这就是个乘法原理 仔细想一下就可以明白

然后我们就来推状态表示

这个状态表示很逆天啊 确实第一次做 肯定时想不出来的

f_i_j 表示的是 将前i-1 列已经固定死 然后 从i-1 列 伸到 i列的方块状态为j的 cnt (很绕 

然后状态计算就是

f_i_j += f_i-1_k 

but k 要合法才行

什么时候不合法?

当 k&j!=0 时 就证明其方框一定有重叠 不合法

还有就是k|j 他的中间0的个数不能是偶数个

我们可以算一下其时间复杂度11个数据2的11次方是2000+

O(2000*2000*11*T)好像可以勉强卡过

但是我们也可以预处理出st【】来判断这个是不是合法的

还可以更优化一点 直接把这个数和另一个数组合合法的直接放进去 到时候直接取出来用就是了

运行时间: 196 ms

结果: Accepted

#include <bits/stdc++.h>
using namespace std;
const int N = 12;
const int M = 1<<12;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int max(int x,int y){return x>y?x:y;}
int min(int x,int y){return x<y?x:y;}
int n,m,f[N][M];
bool st[M];
vector<int>state[M];
signed main(){
    fast
    while(cin>>n>>m,n||m){
        for(int i=0;i<1<<n;i++){
            int cnt=0;st[i]=1;
            for(int j=0;j<n;j++){
                if(i>>j&1){
                    if(cnt%2){st[i]=false;break;}
                }else cnt++;
            }
            if(cnt%2)st[i]=false;
        }
        for(int i=0;i<1<<n;i++) {
            state[i].clear();
            for (int j = 0; j < 1 << n; j++)
                if ((i & j) == 0 && st[i | j])
                    state[i].push_back(j);
        }
        memset(f,0,sizeof f);
        f[0][0]=1;
        for(int i=1;i<=m;i++)
            for(int j=0;j<1<<n;j++)
                for(auto k:state[j])
                    f[i][j]+=f[i-1][k];
        cout<<f[m][0]<<endl;
    }
    return 0^0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值