NYOJ 动态规划

24 篇文章 0 订阅
这篇博客详细介绍了NYOJ上一系列动态规划题目,包括滑雪、括号匹配、矩阵嵌套、单调递增子序列等。博主通过实例解析了动态规划的状态转移方程,并提供了部分题目的解决方案,如最长公共子序列、回文字符串和最大子串和等。此外,还讨论了01背包问题和最长下降子序列,并提到了一些特殊情况的处理,如超级台阶和拦截导弹问题。
摘要由CSDN通过智能技术生成

题1:NYOJ 10(滑雪),前面的博文-动态规划中有。

题2:NYOJ 15(括号匹配),放在区间动态规划中。

题3:NYOJ 16(矩阵嵌套),大概意思给你n个矩阵,这些矩阵相互嵌套,问能够嵌套的最大个数,直接按长度从大到小排序,长度相等则按宽度从大到小排序,由于能够嵌套满足:长1<长2,宽1<宽2,排序后就成了最长下降子序列了。状态转移方程:DP[i]=max(DP[i],DP[j+1])(0<j<i,M[i].b<M[j].b&&M[i].a!=M[j].a)。

 
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAX=1010;
#define max(a,b) (a)>(b)?(a):(b)
#define min(a,b) (a)<(b)?(a):(b)
int n,DP[MAX];
struct Matrix{
    int a,b;
    bool operator<(const Matrix& m) const
    {   if(a!=m.a) return a>m.a;
        if(a!=m.a&&b>m.b) return b>m.b;
    } 
}M[MAX];
int main()
{   int Case,l,w;
    scanf("%d",&Case);
    while(Case--)
    {   scanf("%d",&n);
        for(int i=0;i<n;i++)
        {   scanf("%d%d",&l,&w);
            M[i].a=max(l,w);
            M[i].b=min(l,w);
        }
        sort(M,M+n);
        fill(DP,DP+MAX,1);
        for(int i=0;i<n;i++)
            for(int j=0;j<i;j++)
                if(M[i].b<M[j].b&&M[i].a!=M[j].a) DP[i]=max(DP[i],DP[j]+1);//要加上M[i].a!=M[j].a这个条件
        printf("%d\n",*max_element(DP,DP+n));        
    }
    return 0;
}
        

题4:NYOJ 17(单调递增子序列)。动态转移方程:DP[i]=max(DP[i],DP[j]+1);(0<j<i,s[i]>s[j])。

#include<iostream>
#include<cstring>
#include<cstdio> 
#include<algorithm>
using namespace std;
const int MAX=10010;
#define max(a,b) a>b?a:b
char s[MAX];
int DP[MAX];
int main()
{   int Case;
    scanf("%d",&Case);
    getchar(); 
    while(Case--)
    {   gets(s);
        fill(DP,DP+MAX,1);
        for(int i=0;i<strlen(s);i++)
            for(int j=0;j<i;j++)
                if(s[i]>s[j]) DP[i]=max(DP[i],DP[j]+1);
        printf("%d\n",*max_element(DP,DP+MAX));        
    }
    return 0;
} 

题5:NYOJ 18(数字塔),前面的文章动态规划中有。

题6:NYOJ 36(最长公共子序列),设DP[i][j]为字符串a的前i个字符与字符串b的前j个字符的最长公共子序列。

①、当i,j位置的字符相同,即a[i-1]=b[j-1],那么有DP[i][j]=DP[i-1][j-1]+1

②、若是a[i-1]!=b[j-1],那么有:DP[i][j]=max(DP[i][j-1],DP[i-1][j])

#include<iostream>
#include<cstring>
#include<string>
using namespace std;
const int MAX=1010;
#define max(a,b) (a)>(b)?(a):(b)
#define CLR(arr,val) memset(arr,val,sizeof(arr))
string a,b;
int DP[MAX][MAX];
int main()
{   int Case;
    cin>>Case;
    cin.get();
    while(Case--)
    {   cin>>a>>b;
        CLR(DP,0);
        int n=a.length(),m=b.length();
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
            {   if(a[i-1]==b[j-1]) DP[i][j]=DP[i-1][j-1]+1;
                else DP[i][j]=max(DP[i][j-1],DP[i-1][j]);
            }
        cout<<DP[n][m]<<endl;    
    }
    return 0;
}

题7:NYOJ 37(回文字符串),就是一个字符串,从左到右读和从右到左读是完全一样。那么可以将字符串反转过来(可用string中的reverse函数),然后寻找两个字符串的最长公共子序列。那结果就是字符串长度-最长公共子序列的长度,就是要添加的字符串长度,具体实现和上题一样。

题8:NYOJ 44(最大子串和),用max保存最大子串和,比较简单具体自己看。

#include<iostream>
#include<cstdio>
using namespace std;
const int MAX=1000010; 
int n,a[MAX];
int main()
{   int Case;
    scanf("%d",&Case);
    while(Case--)
    {   scanf("%d",&n);
        for(int i=0;i<n;i++)
            scanf("%d",&a[i]);
        int max=a[0],sum=0;
        for(int i=0;i<n;i++)
        {   sum+=a[i]; //统计某一区间的子串和
            if(max<sum) max=sum;
            if(sum<0) sum=0; 
        }    
        printf("%d\n",max);
    } 
    return 0; 
} 

查看了排名第一的谢武强的代码,他的代码在输入上利用了自己编写的一个函数来实现数据量比较大的输入,我只写下输入的函数。

int Read()
{   char ch;
	int num=0,flag=0;
	ch=getchar();
	if(ch=='-')//判断正负数
	{   flag=1;
		ch=getchar();
	}
	while(ch!=10&&ch!=' ')
	{   num=num*10+ch-'0' ;
		ch=getchar();
	}
	return flag?-num:num;
}

题9:NYOJ 49(开心的小明),01背包,设DP[j]为当价值为j的时候价值与重要度的乘积总和的最大值,那么DP[j]=max(DP[j],DP[j-v]+v*Ip(j>=v)(v为价值,Ip为对应的重要度),代码略。

NYOJ 325(zb的生日)数据量比较小,可以直接搜索,搜索代码见DFS(3),也可以用01背包做,假设两堆西瓜重量分别为a和b,那么两堆之差为:|a-b|=|a-(sum-a)|=|2a-sum|,用DP[j]表示当容量为j时最大的装载量,那么DP[sum/2]就表示其中一堆西瓜的数量,因为两堆西瓜肯定有一堆>=sum/2。那么我就尽量在容量为sum/2中尽量的放入西瓜,使得容量为sum/2的时候西瓜的重量最大,那结果就为:sum-2*DP[sum/2],动态转移方程为:DP[j]=max(DP[j],DP[j-a[i]]+a[i])(j>=a[i]);---其中a[i]为第i个西瓜的重量,sum为西瓜的总重量。下面为NYOJ 325的代码:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int MAX=100010;
#define max(a,b) (a)>(b)?(a):(b)
#define CLR(arr,val) memset(arr,val,sizeof(arr))
int n,sum,a[MAX],DP[MAX];
int main()
{    while(scanf("%d",&n)!=EOF)
    {   sum=0;
        for(int i=0;i<n;i++)
        {   scanf("%d",&a[i]);
            sum+=a[i];        
        }
        CLR(DP,0);
        for(int i=0;i<n;i++)
            for(int j=sum/2;j>=0;j--)
                if(j>=a[i]) DP[j]=max(DP[j],DP[j-a[i]]+a[i]); 
        printf("%d\n",sum-2*DP[sum/2]);        
    }
    return 0;
}        

NYOJ 456(我的邮票分你一半),这个题目数据量稍大,但是用搜索会超时,只能用01背包。代码和上面的差不多改下就行了。

题10:NYOJ 61(传纸条)。代码见多进程DP
题11:NYOJ 76(超级台阶) ,设Fib[i]为台阶为i是的走法,那么可以从i-1的台阶走一步到达i,也可以从i-2位置的台阶一次走二步,所以有:Fib[i]=Fib[i-1]+Fib[i-2]。代码略。

题12:NYOJ 79(拦截导弹),最长下降子序列,代码略。
题13:NYOJ 81(炮兵阵地) ,完全不会动手,过~传说中为状态压缩DP。

题14:NYOJ 104(最大子矩阵和),利用最大子串和(NYOJ 44),可做模板:

#include<iostream>  
#include<cstring>   
#include<cstdio>   
using namespace std;  
const int MAX=110;  
#define CLR(arr,val) memset(arr,val,sizeof(arr))    
int num,n,m,map[MAX][MAX];  
int Maxsum(int a[])  
{   int sum=0,max=a[0];  
    for(int i=0;i<m;i++)  
    {   if(sum>0) sum+=a[i];  
        else sum=a[i];  
        if(sum>max) max=sum;  
    }  
    return max;  
}  
int Matrix()   
{   int max=map[0][0],temp[MAX];  
    for(int i=0;i<n;i++)  
    {   CLR(temp,0);  
        for(int j=i;j<n;j++)  
        {   for(int k=0;k<m;k++)  
                temp[k]+=map[j][k];       
            int sum=Maxsum(temp);  
            if(sum>max) max=sum;  
        }   
    }          
    return max;  
}  
int main()  
{   scanf("%d",&num);  
    while(num--)  
    {   scanf("%d%d",&n,&m);  
        for(int i=0;i<n;i++)  
            for(int j=0;j<m;j++)  
                scanf("%d",&map[i][j]);  
        cout<<Matrix()<<endl;  
    }  
    return 0;  
} 

题15:NYOJ 110(剑客决斗)

题16:NYOJ 119(士兵杀敌),RMQ算法,前面的博文中有代码。

题17:NYOJ 171(聪明的KK),弄懂了NYOJ 61(传纸条),这个就是一盘菜,代码看61的解题报告后,自己写吧,代码略。 


 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值