DP套题练习2

T1:导弹拦截

S1:Q1为最长不上升子序列,Q2为求最长上升子序列.蒟蒻采用的是单调栈的做法,拿求最长上升子序列来说,将a[ 1 ]先入栈,再维护单调上升序列,若大于q[ lenth ],则入栈,若不大于则二分查找,找到单调栈中第一个比它大的数代替,虽然没找到严谨的证明,可以感性理解不断代替大的数的过程,使单调栈的最大值往小的方面发展,是更多数入站,lenth即为答案.Q2用到了Dilworth定理 :最少链划分 = 最长反链长度.

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define e exit(0)
#define R register
int n,len,h[100010],q[100010];
int main()
{
    freopen("missile.in","r",stdin);
    freopen("missile.out","w",stdout);
    scanf("%d",&n);
    for(R int i=1;i<=n;++i)
        scanf("%d",&h[i]);
    q[++len]=h[n];
    for(R int i=n-1;i>=1;--i){
        if(h[i]>=q[len])
            q[++len]=h[i];
        else{
            int id=lower_bound(q+1,q+1+len,h[i])-q;
            q[id]=h[i];
        }
    }
    printf("%d\n",len);
    memset(q,0,sizeof(q));
    len=0;
    q[++len]=h[1];
    for(R int i=2;i<=n;++i){
        if(h[i]>=q[len])
            q[++len]=h[i];
        else{
            int id=lower_bound(q+1,q+1+len,h[i])-q;
            q[id]=h[i];
        }
    }
    printf("%d",len);
    return 0;
}

T2:整数划分

S2:我们可以看出这是一个区间DP.我们设f[ i ][ j ]表示前i个数有j个乘号时的最大乘积j∈[ 0 , i-1 ].分割点k的位置枚举可以从j开始(第j个乘号即在第j个数的后面).f[ i ][ j ] = max{f[ k ][ j - 1]+getv( k +1 , i )}.getv函数即求区间这段数的大小.但在试题中我们还有求出怎么划分,记录分割点,用前驱数组到回去即可.

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define e exit(0)
#define R register
#define ll long long
long long T,n,m,ans,lenth,deep,num[110],f[110][110],q[110],lastr[110][110][3];
void cf(long long x)
{
    deep=0;
    long long len=0,mix[110];
    while(x)
    {    
        mix[++len]=x%10;
        x/=10;
    }
    for(R int i=len;i>=1;--i)
        num[++deep]=mix[i];
}
long long getv(ll x,ll y)
{
    long long sum=0;
    for(R ll i=x;i<=y;++i)
        sum=sum*10+num[i];
    return sum;
}
int main()
{
    freopen("separate.in","r",stdin);
    freopen("separate.out","w",stdout);
    scanf("%lld",&T);
    while(T--){
        lenth=0;
        memset(f,0,sizeof(f));
        memset(q,0,sizeof(q));
        memset(num,0,sizeof(num));
        memset(lastr,-1,sizeof(lastr));
        scanf("%lld%lld",&n,&m);
        cf(n);
        for(R int i=1;i<=deep;++i)
            f[i][0]=getv(1,i);
        for(R int i=1;i<=deep;++i)
            for(R int j=1;j<=deep-1;++j)
                for(R int k=j;k<=i-1;++k)
                    f[i][j]=max(f[i][j],f[k][j-1]*getv(k+1,i));
        for(R int i=1;i<=deep;++i)
            for(R int j=1;j<=deep-1;++j)
                for(R int k=j;k<=i-1;++k)
                    if(f[i][j]==f[k][j-1]*getv(k+1,i)){
                        lastr[i][j][0]=k;
                        lastr[i][j][1]=j-1;
                        lastr[i][j][2]=f[k][j-1];
                    }
        printf("%lld\n",f[deep][m-1]);
        if(f[deep][m-1]==0){
            for(R int i=1;i<=deep;++i)
                printf("%lld ",num[i]);
            printf("\n");
            continue;
        }
        long long lax=deep,lay=m-1;
        while(lax!=-1&&lay!=-1){
            if(lastr[lax][lay][2]!=-1)
                q[++lenth]=lastr[lax][lay][2];
            ll nowx=lastr[lax][lay][0],nowy=lastr[lax][lay][1];
            lax=nowx,lay=nowy;
        }
        long long num=f[deep][m-1]/q[1];
        for(R int i=1;i<=lenth-1;++i)
            q[i]=q[i]/q[i+1];
        for(R int i=lenth;i>=1;--i)
            printf("%lld ",q[i]);
        printf("%lld\n",num);
    }
    return 0;
}

T3:Peter 最近在 R 市开了一家快餐店,为了招揽顾客,该快餐店准备推出一种套餐,该套餐由 A 个汉堡,B 个薯条和 C 个饮料组成.为了提高产量,Peter 从著名的麦当劳公司引进了 N 条生产线.所有的生产线都可以生产汉堡,薯条和饮料,由于每条生产线每天所能提供的生产时间是有限的、不同的,而汉堡,薯条和饮料的单位生产时间又不同.这使Peter 很为难,不知道如何安排生产才能使一天中生产的套餐产量最大.请你编一程序,计算一天中套餐的最大生产量.为简单起见,假设汉堡、薯条和饮料的日产量不超过100 个.

S3:没什么思路,只会f[i][A][B][C]的四维DP,显然时间空间都炸穿.看了题解,发现套餐的个数就是A,B,C中最小能组成的套餐数.我们设f[ k ][ i ][ j ]表示前k个选i个A,j个B时最多还能选多少个C.这里有个枚举的优化,我们确定套数的上限maxn=min ( 100/a,min( 100/b , 100/c ) ),那么要枚举的个数上限即为maxn*a 或 maxn * b.咕咕.

猜你喜欢

转载自www.cnblogs.com/xqysckt/p/11391567.html
今日推荐