借助旧问题回顾动态规划

一切要从一个问题说起,听说那是一道简单的动态规划题,于是我在半夜试着写写,谁知道到了第二天也没有写出来,看着45%的AC率,我想自己的动态规划已经忘得差不多了(呵呵,应该说没有学好)
原问题:

hdu 1466 计算直线的交点数(经典dp)

http://acm.hdu.edu.cn/showproblem.php?pid=1466

平面上有n条直线,且无三线共点,问这些直线能有多少种不同交点数。
比如,如果n=2,则可能的交点数量为0(平行)或者1(不平行)。

分析:变化的根源在于有某些直线是平行的,所有不断产生新的结果(不是单纯的递推得到所有的结果)。对于a条直线,如果有r条直线是相互平行的,那么它相对于i-r直线集合的而言新增加的交点数目就是(i-r)*r。于是得到状态转移式子:if(dp[r][j])  dp[i][(i-r)*r+j]=1; 

如果r条直线的交点情况存在【即全部平行或m个交点】,那么可以用它来讨论平行线相交。后面直线集合的多种平行情况可以由这样的状态转移一次次的算出来。
附上自制图片:

#include <iostream>
#include <cstdio>
using namespace std;

int dp[25][200];

int main()
{
    int n;
    for(int i=1;i<=20;i++){
        dp[i][0]=1;  //平行状态
    }
    for(int i=1;i<=20;i++){
        for(int r=0;r<i;r++){
            int len=i*(i-1)/2;
            for(int j=0;(i-r)*r+j<=len;j++){
                if(dp[r][j]) dp[i][(i-r)*r+j]=1;
            }
        }
    }
    while(cin>>n){
        int length=n*(n-1)/2;
        for(int i=0;i<length;i++)
            if(dp[n][i])printf("%d ",i);
        printf("%d\n",length);
    }
    return 0;
}

痛定思痛,我决定再好好练练DP。

vijos P1098合唱队形 (DP 最长山峰序列长度)

【以前也遇到过类似的问题,最长上升子序列】

https://vijos.org/p/1098

N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。

合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK, 则他们的身高满足T1<...<Ti>Ti+1>…>TK(1<=i<=K)。

你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

分析:

最长山峰序列,即用求最长上升子序列的思路正着跑一次,反正跑一次,然后求出两种结果的长。


#include <iostream>
#include <cstdio>
using namespace std;
int h[110],l[110],r[110],len[110];
int main()
{
    int n;
    while(cin>>n){
        for(int i=0;i<n;i++) scanf("%d",&h[i]);
        l[0]=1;
        for(int i=1;i<n;i++){
            int maxm=0,dex=-1;
            for(int j=0;j<i;j++){
                if(h[j]<h[i]&&maxm<l[j]){
                    maxm=l[j];
                    dex=j;
                }
            }
            if(dex>=0)l[i]=l[dex]+1;
            else l[i]=1;
        }
        //for(int i=0;i<n;i++) cout<<l[i]<<" ";  cout<<endl;
        r[n-1]=1;
        for(int i=n-2;i>=0;i--){
            int maxm=0,dex=-1;
            for(int j=n-1;j>i;j--){
                if(h[j]<h[i]&&maxm<r[j]){
                    maxm=r[j];
                    dex=j;
                }
            }
            if(dex>=0)r[i]=r[dex]+1;
            else r[i]=1;
        }
        int ans=0;
        for(int i=0;i<n;i++){
            len[i]=l[i]+r[i]-1;
            //cout<<len[i]<<" ";
            ans=ans>len[i]?ans:len[i];
        }   //cout<<endl;
        printf("%d\n",n-ans);
    }
    return 0;
}


Vijos P1303导弹拦截(dp+greedy)

https://vijos.org/p/1303

相似问题:

hdu 1257 最少拦截系统(贪心)
相关博客:http://blog.csdn.net/thearcticocean/article/details/48031781

本题变化:“输出数据只有一行,该行包含两个数据,之间用半角逗号隔开。第一个数据表示这套系统最多能拦截的导弹数;第二个数据表示若要拦截所有导弹至少要再添加多少套这样的系统。”

最长不升子序列长度——第一问答案,使用DP:


第二问仍然是用贪心做的。

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int h[25],tag[25],dp[25];
int main()
{
    while(~scanf("%d",&h[0])){
        int len=1;
        while(scanf(",%d",&h[len])) len++;
        memset(dp,0,sizeof(dp));
        memset(tag,0,sizeof(tag));
        dp[0]=1;
        int ans1=1;
        for(int i=1;i<len;i++){
            int maxm=0,dex=-1;
            for(int j=0;j<i;j++){
                if(h[i]<h[j]&&maxm<dp[j]){
                    maxm=dp[j];
                    dex=j;
                }
            }
            if(dex>=0)dp[i]=dp[dex]+1;
            else dp[i]=1;
            if(ans1<dp[i]) ans1=dp[i];
        }
        int ans2=0;
        for(int i=len-1;i>=0;i--){
            if(tag[i]==0){
                 ans2++;
                 tag[i]=1;
                 int temp=h[i];
                 for(int j=i-1;j>=0;j--){
                    if(h[j]>temp&&tag[j]==0){ tag[j]=1; temp=h[j]; }
                 }
            }
        }
        printf("%d,%d\n",ans1,ans2-1);
    }
    return 0;
<span style="font-size:14px;">}</span>

VIJOS P1122出栈序列统计 (catalan数 | DP)

https://vijos.org/p/1122

栈是常用的一种数据结构,有n令元素在栈顶端一侧等待进栈,栈顶端另一侧是出栈序 列。你已经知道栈的操作有两·种:push和pop,前者是将一个元素进栈,后者是将栈顶元素弹出。现在要使用这两种操作,由一个操作序列可以得到一系列 的输出序列。请你编程求出对于给定的n,计算并输出由操作数序列1,2,…,n,经过一系列操作可能得到的输出序列总数。

分析:按照在某一个时刻的弹出情况,数字要么弹出,要么不弹出,所以有6种情况才对,4的话结果应该是6*4=24.(隔板)然而情况却不是这样,因为不存在3,1,2这样的结果,如果3先弹出,那么1,2全部先压进栈了才对,所以只有3,2,1。通过"压入"暂时看不出递推式,因为我们子问题的结果是弹出的结果数,所以也应该向弹出的情况发展思考,刚刚考虑的是第一个出来的数字,现在想想最后一个出来的数字,设它是i,那么比它小的数字1——i-1在它之前要弹出,比它大的数字也要先弹出,于是结果出来了:



#include <iostream>
#include <cstdio>
usingnamespacestd;
int f[20];
int main()
{
    f[0]=1;
    f[1]=1;
    for(int i=2;i<=15;i++){
        for(int j=1;j<=i;j++){  //第i个元素最后出去
            f[i]+=f[j-1]*f[i-j];
        }
    }
    int n;
    while(cin>>n){
        printf("%d\n",f[n]);
    }
    return0;
}

但是后来听别人说这是catalan数,又涨姿势了。。catalan数的另一种递推形式:



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值