【笔记】dp初步

 · 笔记 · 

0.动态规划:

  将一个复杂的问题分解成若干个子问题,通过综合子问题的最优解来得到原问题的最优解

1.理解“状态”“状态转移方程”

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

①最优子结构:一个问题的最优解是由它的各个子问题的最优解决定的。
②重叠子问题

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

3.实现方法:

①递推

②递归(记忆化搜索)

4.时间复杂度:

  状态总数*每个状态的决策时间 

 · DAG上的动态规划 · 

1.嵌套矩形

模型:求DAG中不固定起点的最长路 

相似模型 最长上升子序列

  题面:有n个矩形,每个矩形可以用a,b来描述,表示长和宽。矩形X(a,b)可以嵌套在矩形Y(c,d)中当且仅当a<c,b<d或者b<c,a<d(相当于旋转X90度)。例如(1,5)可以嵌套在(6,2)内,但不能嵌套在(3,4)中。你的任务是选出尽可能多的矩形排成一行,使得除最后一个外,每一个矩形都可以嵌套在下一个矩形内。

        测试数据的第一行是一个正正数n,表示该组测试数据中含有矩形的个数(n<=1000)。随后的n行,每行有两个数a,b(0<a,b<100),表示矩形的长和宽

//设dp[i]为从i出发的最长路长度
//则可以很方便的打印出字典序最大/小的最长路 

//也可以设dp[i]为到i结尾的最长路长度 
#include<cstdio>
#include<cstdlib>
using namespace std;
int dp[N];
int DP(int i)
{
    int &ans=d[i];//为表项d[i]声明一个引用ans 
    if(ans>0 ) return ans;
    
    ans=1;
    for(int j=1;j<=n;j++)
        if(G[i][j] ) ans=max(ans,dp[j]+1);
    return ans;
}
void print_ans(int i)//打印方案 且方案字典序最小 
{
    printf("%d ",i);
    for(int j=i;j<=n;j++)
        if(G[i][j] && d[i]==d[j]+1 )
        {
            print_ans(j);
            break;
        }
}

int main()
{
    for(int i=1;i<=n;i++)
        DP(i);
    return 0;
}
View Code

2.硬币问题

  模型:固定终点的最长路和最短路

  题面:有n种硬币,面值分别为v1v2v3···,vn,数量无限。输入非负整数s。要求输出最少的硬币组合

 

(我还没写完)

 · 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("\n");
}

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\n",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\n",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\n",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\n",ans2);
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&d[i]);
    
    dp1();
    dp2(); 
    return 0;
}
View Code

 

posted @ 2019-10-29 21:52  心若笺诗  阅读(238)  评论(0编辑  收藏  举报