21.11.28周末总结(dp重要题解)

未来可能要复习英语和期末考试(从14周就开始了),刷题可能会少了

下面记录的是略有意义的题
都是我想不到的规划方法

1、两个字符串比较,改值

设A和B是两个字符串。我们要用最少的字符操作次数,将字符串A转换为字符串B。
1、删除一个字符;
2、插入一个字符;
3、将一个字符改为另一个字符;

①、f [ i ] [ j ] , 表示代表字符串A的前i个字符变为字符串B的前 j 个需要多少步
②、赋初始值,求最少则赋大值 ;
除此之外,每个取值都与上一步骤有关,因此要赋正确的值
例如,A的第0个到B的第 j 个,最不理想就是要换 j 步
③、
删除:f (i-1,j)+1 // 把字符串的第i个字符删除了,上一步的A[i-1] 和 B[j] 对应,将上一步加1
添加:f (i,j-1)+1 //将B[j]字符加在A字符串的最后面,A[i+1] 和 B[j] 对应,上一步加1
替换:f (i-1,j-1)+1 //字符串A和B的最后两个都相等了,上一步加一
不变:f (i-1,j-1) //字符串A和B的最后两个都相等,不改变
④、需要用到上一步,则从1开始循环 。// if (s [ i - 1 ] == s s [ j - 1 ] ) f [ i ][ j ] = f [ i - 1] [ j - 1 ] ;
注意答案无需减一

for(int i=0;i<N;i++) 
{
    for(int j=0;j<N;j++)
    {
        f[i][j]=50005;
    }
}
int l1=s.length();
int l2=ss.length();
for(int i=0;i<=l2;++i)
     f[0][i]=i;
for(int i=0;i<=l1;++i)
     f[i][0]=i;
for(int i=1;i<=l1;i++)
{
    for(int j=1;j<=l2;j++)
    {
        if(s[i-1]==ss[j-1]) f[i][j]=f[i-1][j-1];
        else
        {
            f[i][j]=min(f[i][j],f[i-1][j]+1);
            f[i][j]=min(f[i][j],f[i][j-1]+1);
            f[i][j]=min(f[i][j],f[i-1][j-1]+1);
        }
    }
}
cout<<f[l1][l2]<<endl;
2、两个限制条件(两种方法)

班里共有 n 位学生,将分成两队互相竞争,并要求这些同学综合能力测试的成绩之和在不超过班级总分一半的前提下尽量达到最高。这样分成的两队实力是最平均的
即,选出n/2个人来,使得他们的分可以装在sum/2大小的包里,最多可以装多少
①、两重循环,外多加一个数组

cin>>n;
sum=0;
for(int i=1;i<=n;i++)
{
    cin>>a[i];
    sum+=a[i];
}
sum=sum/2;
x=0;
for(int i=1;i<=n;i++)
{
    for(int j=sum-1;j>=a[i];j--)
    {
        if(f[j-a[i]]+a[i]>f[j] && b[j-a[i]]<n/2)
        {
            f[j]=f[j-a[i]]+a[i];
            b[j]=b[j-a[i]]+1;
        }
    }
}
cout<<f[sum-1]<<endl;

②、三重循环,且二维数组

cin>>n;
sum=0;
for(int i=1; i<=n; i++)
{
    cin>>a[i];
    sum+=a[i];
}
sum=sum/2;
x=0;
f[0][0]=1;
for(ull i=1; i<=n; i++)
{
    for(int k=min(i,n/2); k>=1; k--)
    {
        for(int j=sum; j>=a[i]; j--)
        {
            f[k][j]=max(f[k][j],f[k-1][j-a[i]]);
        }
    }
}
for(int i=sum;i>=0;i--)
{
    if(f[n/2][i]) {cout<<i<<endl;break;}
}
3、最大分组数

第 i 头奶牛的理智度为 ai ,他打算将所有的奶牛隔离成若干个小组,每个小组内的奶牛的理智度总和都要不小于零。
小组内的奶牛位置必须是连续的。请帮助约翰计算一下,最多分成几组。

类似于最大子段和之类的题,f 数组用来记录前n个数的最多分组数
子段和 => 前缀和数组存储
初始化:f , 当前缀和不小于 0 的时候,前面至少可以分成一组

其实最初想法就是一个负值的数,然后去找他前后的不为0的最小长度
公式推导,其实当前 i 能分成最多的数组数是,以当前值为终点的不为0的最小长度

for(int i=1;i<=n;i++)
{
    cin>>a[i];
    a[i]=a[i-1]+a[i];//前缀和
    if(a[i]>=0) f[i]=1;//初始化
}
for(int i=1;i<=n;i++)
{
    for(int j=1;j<i;j++)
    {
        //条件:前缀和不小于 0 
        //当这一子段和也不小于时,就意味着可以多加一组 
        if(f[i]>0 && a[i]-a[j]>=0) f[i]=max(f[i],f[j]+1);//f数组存储i以前最多可分多少组 
    }
}
4、几套系统?(最长上升序列长度)

第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。如果要拦截所有导弹最少要配备多少套这种导弹拦截系统?(即,一套系统可以攻击一组不上升序列长度)
例: 389 207 155 300 299 170 158 65
简单介绍:第一次可以踢去一组不上升序列长度,问需要踢几次

根据Dilworth定理优化 :“序列的不下降子序列最少划分数”(我没听过反正,记就是了)
最终就是求一个 最长上升序列长度

int l=1;
dd[1]=a[1];
for(int i=2; i<=n; i++)
{
    if(dd[l]<a[i]) dd[++l]=a[i];
    else
    {
        int p=lower_bound(dd+1,dd+1+l,a[i])-dd;
        dd[p]=a[i];
    }
}
cout<<l<<endl;
5、不特别,但是我想不到方法

构造一个 n 位数,并且满足以下两个要求:
1、这个数没有前导零,0 没有前导零且是一位数。2、这个数的所有数位中有偶数个 k,0 是偶数。

思路:
对于一个满足要求的 i 位数,可以由一个不满足要求的 i−1 位数加上一位 k 或者由一个满足要求的 i−1 位数加上一位除了 k 之外的数位得来。
同理,对于一个不满足要求的 i 位数,可以由一个满足要求的 i 位数加上一位 k 或者由一个不满足要求的 i−1 位数加上一位除了 k 之外的数得来。

f[1]=8;//不选k //满足条件
dp[1]=1;//选k  //不满足条件
for(int i=2; i<=100005; i++) //i个k
{
    f[i]=(f[i-1]*9+dp[i-1])%mod;
    dp[i]=(dp[i-1]*9+f[i-1])%mod;
}
scanf("%d",&t);
while(t--)
{
    scanf("%d%d",&n,&k);
    if(n==1) printf("%d",9);
    else printf("%d\n",f[n]);
}
6、缩缩缩

奶牛们分2批就餐,卡片上的号码是完全杂乱无章的,奶牛们不动,他沿着队伍从头到尾走一遍,把那些他认为排错队的奶牛卡片上的编号改掉,最终得到一个他想要的每个组中的奶牛都站在一起的队列,例如112222或111122。全为1 或2 都可以
求最少改变数。

最初想法:受上面第五题影响,想到了两个数组,一个存全改为1时,当前位置所需最小步数;另一个则存全改2时 ;
然后,f 的前部+g的后部表示:前面全改为1,后面全改为2。然后特判全改1或2 。一直保留最小值即可。

for(int i=1;i<=n;i++)
{
    cin>>a[i];
    if(a[i]==1) g[i]=g[i-1]+1,f[i]=f[i-1];
    else f[i]=f[i-1]+1,g[i]=g[i-1];
}
sum=0x3f3f3f3f;
for(int i=1;i<n;i++)
{
    sum=min(f[i]+g[n]-g[i],sum);
    //cout<<sum<<' ';
    //sum=min(ff[i+1]+gg[i],sum);
    //cout<<sum<<endl;
}
sum=min(sum,f[n]);
sum=min(sum,g[n]);
cout<<sum<<endl;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值