Day7——动态规划

今天讲动态规划啊,动规啊,DP啊,啊啊啊。
今天给我们讲课的是一个女生,据说还在读高三。于是今天上课,前几天给我们讲课的两个大佬都过来了,考试的时候也来了。废话不多说。今天似乎又爆零了!!!


DP今天讲了

  • 序列DP
  • 背包问题
  • 区间DP
  • 状压DP

今天讲了好几道例题,还有几道考题是例题,有的是一样的,有的是稍加修改。


第一题:序列

【问题描述】
有一个长度为N的序列A,求这个序列中两个不相交子串的最大和A。
【输入格式】
第一行,一个正整数 N,表示序列总长度。
第二行有 N 个整数,即序列A。
【输出格式】
输出数据包含一个自然数,即整数S。
【数据规模】
对于30%的数据,2≤N≤80。
对于100%的数据 2≤N≤100000,-10000≤A≤10000。

这题与老师讲的两端最大字段和。

解:

对于30% 的数据,有2 ≤ N ≤ 80
枚举两个区间的左端点、右端点,利用前缀和O(1) 统计答案。
时间复杂度O(n4)
对于100% 的数据,有2 ≤ N ≤ 100000
由于是两段, 因此必然会有一个分界点, 可以从这里把序列切开。
那么,我们只要O(N) 枚举分界点i,如果能快速求出[1; i]
和[i + 1; N] 中的最大子段和,那么本题就能成功解决。
f [i] 表示[1; i] 中的最大子段和,g[i] 表示[i + 1; N] 中的最大子段
和,预处理f [i]; g[i],即正着一遍、反着一遍做最大子段和即可。
时间复杂度O(N)。

代码:
#include<cstdio> 
#define N 100010
int n,a[N],f[N],g[N],ans=-1<<20;
#define cut(a) (a>0?a:0)
#define cmax(a,b) (a<b?a=b:1)
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",a+i),f[i]=cut(f[i-1])+a[i];f[0]=f[n+1]=g[0]=g[n+1]=ans;
    for(int i=n;i;i--)g[i]=cut(g[i+1])+a[i];
    for(int i=n;i;i--)cmax(g[i],g[i+1]);
    for(int i=1;i<=n;i++)cmax(f[i],f[i-1]),cmax(ans,f[i]+g[i+1]);
    printf("%d\n",ans);
}

第二题:方方方数列

【问题描述】
方方方有一个由n个整数构成的数列,并且他能把其中的0变为任何数,当然,他也可以选择不改变。方方方想知道他能得到的最长递增子序列的长度,但是他认为这太简单了并且他要去准备高考了,所以把这个问题丢给了你。
【输入格式】
第一行1个正整数n,表示数列的长度。
接下来一行为n个整数,表示这个数列。
【输出格式】
输出数据包含一个自然数,即方方方能得到的最长递增子序列的长度。
【数据规模】
对于40%的数据,1≤n≤10000,且数列中没有数字0。
对于100%的数据, 1≤n≤10000。

解:

0可以转化成任意整数,包括负数,显然求LIS时尽量把0都放进去
必定是正确的。因为0放不进去的原因无非就是非严格递增。
如何保证严格递增?
我们可以将每个数a[i]减去i前面0的个数,再做LIS,就能保证结果
是严格递增的。
因此我们可以把0剔除,对剩下的数列做LIS,统计结果的时候再算
上0的个数即可。
时间复杂度O(nlogn)。
代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+50;
const int M=1e9+7;
int t,n,a[N],dp[N],num[N];
int erfen(int l,int r,int u){
while(l<=r){
    int mid=(l+r)>>1;
    if(dp[mid]<a[u]&&dp[mid+1]>=a[u])
        return mid;
    else if(dp[mid]<a[u])
        l=mid+1;
    else
        r=mid-1;
}
    return 0;
}
int main()
{
    int cnt0=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        dp[i]=1000000+10,num[i]=0;
    dp[0]=0,num[0]=0;
    int i,j,cnt=0;
    for(i = 1;i <= n;i ++){
        scanf("%d",&a[i]);
        if(a[i]==0)
            cnt++,cnt0++;
        if(a[i]!=0)
        num[i]=cnt;
    }
    for(i=1,cnt=0;i<=n;i++){
        if(a[i]!=0){
        a[++cnt]=a[i]-num[i];
        }
    }

    int len=0;
    dp[1]=min(dp[1],a[1]);
    len++;
    if(cnt==0)
    len=0;
    for(i = 2;i <= cnt;i ++){
        if(a[i]>dp[len])
            j=++len;
        else j=erfen(1,len,i)+1;
        dp[j]=a[i];
    }

   printf("%d",len+cnt0);
    return 0;
}

第三题:删数

(remove.cpp/c/pas)
【问题描述】
有N个不同的正整数 排成一排,我们可以从左边或右边去掉连续的i(1≤i≤n)个数(只能从两边删除数),剩下N-i个数。再把剩下的数按以上操作处理,直到所有的数都被删除为止。
每次操作都有一个操作价值,比如现在要删除从i位置到k位置上的所有的数。操作价值为 ,如果只去掉一个数,操作价值为这个数的值。
问如何操作可以得到最大值,求操作的最大价值。
【输入格式】
第一行为一个正整数N。
第二行为N个用空格隔开的N个不同的正整数。
【输出格式】
输出数据包含一个自然数,表示操作的最大值。
6 768
54 29 196 21 133 118
【输入输出样例】
经过3 次操作可以得到最大值,第一次去掉前面3个数54、29、196,操作价值为426。第二次操作是在剩下的三个数(21 133 118)中去掉最后一个数118,操作价值为118。第三次操作去掉剩下的2个数21和133 ,操作价值为224。操作总价值为426+118+224=768。
【数据规模】
对于10%的数据,3≤N≤20。
对于100%的数据 3≤N≤100。

这题得要有样例,不然理解很难。

解:

N个不同正整数组成的序列,每次可从左边或右边去掉连续的i 个
数,重复操作直到所有的数都被删除为止。删除从i 位置到k 位置上的
所有数的操作价值为| xi × xk |(k − i + 1),求操作的最大价值和。
3 ≤ N ≤ 100
区间DP,设f [i][j]表示区间[i; j]的最大操作价值。
枚举k表示接下来删到哪位即可。
时间复杂度O(N3)
代码:

#include<bits/stdc++.h>
using namespace std;
const int MaxN=101;
int n;
int a[MaxN],f[MaxN][MaxN],d[MaxN][MaxN];
void init()
{int i,j;
 scanf("%d",&n);
 for (i=1;i<=n;i++)
   scanf("%d",&a[i]);//第i个数的值,也是删除第i个数得到的操作价值 
 for (i=1;i<=n;i++)
   {d[i][i]=a[i];
    for (j=i+1;j<=n;j++)
      d[i][j]=abs(a[i]-a[j])*(j-i+1);//d[i][j]是一次性删除第i到第j个数得到的操作价值 
   }  
}
void dp()
{int len,i,j,k;
 memset(f,0,sizeof(f));
 for (i=1;i<=n;i++)
   f[i][i]=a[i];
 for (len=1;len<=n-1;len++)
   for (i=1;i<=n-len;i++)
     {j=i+len;
      f[i][j]=d[i][j];//f[i][j]表示按照规则删除数列a[i]..a[j]得到的最大值 
      for (k=i;k<=j-1;k++)
        f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]);
     }
 printf("%d\n",f[1][n]);
}
int main()
{
 init();
 dp();
 return 0;
}

第四题:探险

【问题描述】
方方方去遗迹探险,遗迹里有N个宝箱,有的装满了珠宝,有的装着废品。
方方方手上有老大的地图,所以他知道每一个宝箱的价值,但是他不喜欢
走回头路,所以要按顺序拿这N个宝箱中的若干个。
但是拿宝箱很累的。一开始方方方的体力是1,每得到一个宝箱之后,方方方得到的价值是体力×宝箱的价值,之后他的体力就会变为原来的k倍(0

解:

对于100% 的数据,有1 ≤ M ≤ N ≤ 100000。
观察下列式子
i + 2k + 3k2 + 4k3 = [(4k + 3) × k + 2] × k + 1
从后往前考虑,在某次乘了k 以后,后面的结果也都会乘上k,和
后面取了多少个数无关。
我们用f [i] 表示[i; N] 的答案。
f [i] = maxff [j] × k + A[i]g (i < j ≤ i + M)
直接用单调队列维护即可。正难则反。

代码:
#include<bits/stdc++.h>
using namespace std; 
int n,m,q[100010],a[100010],l,r;
double k,f[100010];
int main(){
    scanf("%d%d%lf",&n,&m,&k);
    for(int i=1;i<=n;i++)scanf("%d",a+i);
    for(int i=n;i;q[++r]=i--){
        if(l<=r&&q[l]>m+i)l++;
        f[i]=f[q[l]]*k+a[i];
        while(l<=r&&f[q[r]]<=f[i])r--;
    }
    if(l<=r&&q[l]>m)l++;
    printf("%.2lf\n",f[q[l]]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值