【刷题】dp入门总结

 · 笔记 · 

动态规划求解的问题,一般有两个特征:

①最优子结构
②重叠子问题

所以算法的设计思路不在于一下子就想到了某个问题可以使用DP算法,
而在于先看能不能用穷举法,如果可以用问题可以分解,分治法+穷举可以解决;
如果问题包含重叠字问题,并且是求解最优解,那么此时用动态规划。

 · tips · 

【1】精度问题

【2】重叠子问题

【3】初始化

【4】后效性

· 要点归纳(个人) · 

【1】卡常问题:

 (1)sqrt能只开一次就只开一次,

我次次都开,tle 6个点

(2)精度问题

不要先开sqrt再加,可能丢失精度,虽然这里问题不大

(3)不四舍五入 的 输出问题

 最后代码:

#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
int n,m;
const int N=1003;
int sum[N],st[N][N],f[403][N];

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&sum[i]),sum[i]= sum[i]*sum[i] + sum[i-1];
    
    //memset(f,0x3f,sizeof(f));
    for(int i=1;i<=n;i++)
        f[1][i]=sum[i];
    for(int k=2;k<=m;k++)
        for(int i=k;i<=n;i++)
        {
            f[k][i]=max(f[k-1][k-1],sum[i]-sum[k-1] );
            for(int j=k+1;j<=i;j++)
                f[k][i]=min(f[k][i],max(f[k-1][j-1],sum[i]-sum[j-1] ));
        }
    int ans=(double)(sqrt((double)f[m][n]))*100;
    //printf("%d.%d",ans/100,ans%100);//错误写法:可能丢了1个前导0,最后没有两位小数
    printf("%.2lf",ans/100.0);
    return 0;
} 
View Code

【2】逢低购买

先求最长的长度,以减少dp的复杂度

然后我想的是,记录f[k][i],以min(mx[i],mx[j]+1) * n* n的复杂度dp

但是!!!

可以压维成f[i],他的k是mx[i],并且这样是保证找到所有方案的!!!

直接不tle了

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
using namespace std;
int n;
const int N=5003;
long long d[N];
int ans,mx[N];

struct hp
{
    int len,d[100];
    hp()
    {
        len=1;
        memset(d,0,sizeof(d));
    }
    
    hp operator + (const hp & b ) const
    {
        hp ans;
        ans.len =max(len,b.len );
        int jw=0;
        for(int i=1;i<=ans.len ;i++)
        {
            ans.d[i] =d[i] + b.d[i] +jw ;
            jw=ans.d[i] /10;
            ans.d[i] %=10;
        }
        if(jw) ans.d[++ans.len ]=jw;
        return ans;
    }
}f[N];
void print(hp a)
{
    for(int i=a.len ;i;i--)
        printf("%d",a.d[i] );
}

long long a[N];
void get_mx_time()
{
    int len=0;
    for(int i=1;i<=n;i++)
    {
        int pos=lower_bound(a+1,a+len+1,-d[i])-a;
        a[pos]=-d[i];
        
        if(pos>len) len++;
        mx[i]=pos;
    }
    ans=len;
}

signed main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%lld",&d[i]);
    get_mx_time();
    printf("%d ",ans);
    
    f[1].len =1,f[1].d[1] =1;
    for(int i=2;i<=n;i++)
    {
        if(mx[i]==1) f[i].len =1,f[i].d[1] =1;
        for(int j=i-1;j;j--)
            if(d[j]>d[i] && mx[i]==mx[j]+1 )
                f[i]=f[i]+f[j];
            else if(d[j]==d[i] && mx[i]==mx[j] )
            {
                f[i].len =1,f[i].d[1] =0;
                break;
            }
    }
    hp tmp;
    for(int i=ans;i<=n;i++)
        if(mx[i]==ans)
            tmp=tmp+f[i];
    print(tmp);
    return 0;
}
View Code

但是UVA的原题需要高精度,虽然我不知道是怎么看出来的,

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
using namespace std;
int n;
const int N=5003;
int d[N];
int mx[N];

struct hp
{
    int len,d[100];
    hp()
    { mem(); }
    void mem()
    {
        memset(d,0,sizeof(d));
        len=1;
    }
    
    hp operator + (const hp b ) const
    {
        hp ans;
        ans.len =max(len ,b.len );
        int jw=0;
        for(int i=1;i<=ans.len ;i++)
        {
            ans.d[i] =d[i] + b.d[i] +jw ;
            jw=ans.d[i] /10;
            ans.d[i] %=10;
        }
        if(jw) ans.d[++ans.len ]=jw;
        return ans;
    }
}f[N];
void print(hp a)
{
    for(int i=a.len ;i>0;i--)
        printf("%d",a.d[i] );
    printf("
");
}

int a[N];
int get_mx_time()
{
    int len=0;
    for(int i=1;i<=n;i++)
    {
        int pos=lower_bound(a+1,a+len+1,-d[i])-a;
        a[pos]=-d[i];
        
        if(pos>len) len++;
        mx[i]=pos;
    }
    return len;
}

signed main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&d[i]);
    int ans=get_mx_time();
    printf("%d ",ans);
    
    for(int i=1;i<=n;i++)
    {
        if(mx[i]==1) f[i].len =1,f[i].d[1] =1;
        for(int j=i-1;j>0;j--)//碰到最右边的一个相等的量,才清空 
            if(d[j]>d[i] && mx[i]==mx[j]+1 )
                f[i]=f[i]+f[j];
            else if(d[j]==d[i] && mx[i]==mx[j] )
                f[j].mem() ;
    }
    hp tmp;
    tmp.mem();
    for(int i=ans;i<=n;i++)
        if(mx[i]==ans)
            tmp=tmp+f[i];
    print(tmp);
    return 0;
}
View Code

 【3】花店橱窗布置

很顺手的写出了方程,然后掉了初始化!!!

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m;
const int N=103,M=103;//花瓶数量,花的数量 
int g[N][M],f[N][M],pre[N][M];//花瓶对应花 

void get_plan(int i,int j)
{
    if(j>1)
        get_plan(pre[i][j]-1,j-1);
    printf("%d ",pre[i][j]);
}

int main()
{
    scanf("%d%d",&m,&n);
    for(int i=1;i<=m;i++)
        for(int j=1;j<=n;j++)
            scanf("%d",&g[j][i]);
    
    memset(f,128,sizeof(f));
    //先更新i-1号花瓶装0-i朵花 
    for(int i=1;i<=n;i++)//第i号花瓶 
    {
        f[i][0]=0; 
        for(int j=1;j<=i;j++)
            if(f[i-1][j-1]+g[i][j] > f[i-1][j] )
                f[i][j]=f[i-1][j-1]+g[i][j],pre[i][j]=i;//我装花的状态从i-1,j-1来,所以我想装花,要i-1,j-1状态为0,
                //如果不将f[0][0]=0,会导致1号花瓶不能装花 
            else
                f[i][j]=f[i-1][j],pre[i][j]=pre[i-1][j];
    }
    printf("%d
",f[n][m]);
    get_plan(n,m);
    return 0;
}
View Code

 【4】迎接仪式

 感谢luogu题解区大佬:

 然后是我的思路:

(交换型dp get)

//最后一维表示最后一个字母是什么  

//a[i]=='j'
//前面是'j',后面可以换成'z'
//        f[nn-1][i][j-1][0]
//    不换的时候,f[nn-1][i][j][1] 
//前面是'z',前后调换
//        f[nn-1][i-1][j-1][1] +1 
//    不换的时候,f[nn-1][i][j][1] 

//a[i]=='z'
//前面是'j',不用换直接加 
//        f[nn-1][i][j][0] +1 
//前面是'z',把前面换成'j' 
//        f[nn-1][i-1][j][1] 
//    不换的时候,f[nn-1][i][j][1] 

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
using namespace std;
int n,K; 
const int N=503,M=103;
char ch[503];
int d[N],f[N][M][M][2];

int main()
{
    scanf("%d%d",&n,&K);
    scanf("%s",ch+1);
    for(int i=1;i<=n;i++)
        if(ch[i]=='z') d[i]=1;
    
    memset(f,128,sizeof(f));
    f[0][0][0][1]=0;
    for(int nn=1;nn<=n;nn++)
        for(int i=0;i<=K;i++)//前面通过交换多了几个'j' 
            for(int j=0;j<=K;j++)//多了几个'z' 
                if(!d[nn] )//'j'
                {
                    f[nn][i][j][0]=max(f[nn-1][i][j][1],f[nn-1][i][j][0]);
                    if(j>0)
                        f[nn][i][j][1]=max(f[nn-1][i][j-1][0]+1,f[nn-1][i][j-1][1]);
                }
                else
                {
                    f[nn][i][j][1]=max(f[nn-1][i][j][0]+1,f[nn-1][i][j][1]);
                    if(i>0)
                        f[nn][i][j][0]=max(f[nn-1][i-1][j][0],f[nn-1][i-1][j][1]);
                }
    
    int ans=0;
    for(int i=0;i<=K;i++)
        ans=max(ans,max(f[n][i][i][0],f[n][i][i][1]));
    printf("%d
",ans);
    return 0;
}
View Code

【5】环形最大连续子序列

神奇O(n)解法

以后碰到可能从中间断开的环,求极值,都可以从从最大最小的差来思考!!!

【6】最大联通子树和

#include<cstdio>
#include<cstdlib>
#include<queue>
using namespace std;
int n,ans1,ans2;
const int N=5e4+3;
int d[N],a[N];

int work()//求数组中最大连续子段和 
{
    int as=-200;
    int pre=0,nw;
    for(int i=1;i<=n;i++)
    {
        nw=a[i];
        if(pre>0) nw+=pre;
        
        pre=nw;
        as=max(as,pre);
    }
    return as;
}
void dp1()//环形 最大 连续子段和 的神奇O(n)求法 
{
    int sum=0;
    for(int i=1;i<=n;i++) a[i]=d[i],sum+=d[i]; 
    ans1=work();
    
    for(int i=1;i<=n;i++) a[i]=-a[i]; 
    ans1=max(ans1,sum+work());
    
    printf("%d
",ans1);
}

int head[N],tot;
int ev[N<<1],enx[N<<1];
void add(int u,int v)
{    ev[++tot]=v,enx[tot]=head[u],head[u]=tot;
    ev[++tot]=u,enx[tot]=head[v],head[v]=tot; }

int f[N];//强制包括rt,和rt的子树的最大联通子树和 
void dfs(int rt,int fa)
{
    f[rt]=d[rt];
    for(int i=head[rt];i;i=enx[i])
    {
        if(ev[i]==fa) continue;
        dfs(ev[i],rt);
        
        if(f[ev[i]]>0) f[rt]+=f[ev[i]];
    }
    ans2=max(ans2,f[rt]);
}
void dp2()
{
    int u,v;
    for(int i=1;i<n;i++)
        scanf("%d%d",&u,&v),add(u,v);
    dfs(1,0);
    printf("%d
",ans2);
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&d[i]);
    
    dp1();
    dp2(); 
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/xwww666666/p/11761825.html