4.24周总结

本文回顾了作者在Codeforces比赛中遇到的难题,解释了对最小字典序数组的理解误区,并分享了AC代码。探讨了递归函数和区间动态规划在解题中的优势和效率问题,涉及了几个实例,如穿衣服最少件数、配对计数等。最后,作者总结了递归函数在区间DP中的应用心得,强调了其清晰度和可读性,但也提及了效率上的挑战。
摘要由CSDN通过智能技术生成

4.24周总结

4.21Codeforces Round #717 (Div.2)
这个A题没做出来的原因竟然是不清楚最小字典序数组是什么意思,在做完题之后才知道数字的字典序只是从第一个数字到最后一个数字按照先后顺序排列形成的一个数的最小值!!! 而且也并没有规定数组的每一个数必须是个位数!!!害
比赛后的敲出的AC代码:

#include <bits/stdc++.h>
using namespace std;

int T;
int n,k;
int a[105];

int main(){
	cin>>T;
	while(T--){
		cin>>n>>k;
		for(int i=1;i<=n;i++){
			cin>>a[i];
		}
		for(int i=1;i<n;i++){
			if(a[i] < k) a[n] += a[i],k -= a[i],a[i] = 0;
			else{
				a[n]+=k,a[i] -= k,k = 0;
				break;
			}
		}
		for(int i=1;i<=n;i++){
			cout<<a[i]<<' ';
		}
		cout<<endl;
	}
	return 0;
}

在CF上打完比赛,做赛后分析时,看别人的代码的时候,也看到过先定义一个函数,然后再写主函数的,之前感觉这种办法不麻烦吗?还得再写一遍自己写的函数,现在到了区间dp,越来越发现这种办法的实用性,可以增加代码的易读性,可以更好的理解代码,也可以让写代码的人思路更加清晰,在修改代码时也更加方便,只有一个缺点,就是不太好想…
我也在csdn上找了一下大家的观点,摘抄了几句,如下:
缺点:
函数调用的时候,每次调用时要做地址保存,参数传递等,这是通过一个递归工作栈实现的。具体是每次调用函数本身要保存的内容包括:局部变量、形参、调用函数地址、返回值。那么,如果递归调用N次,就要分配N次局部变量、N次形参、N次调用函数地址、N次返回值,势必效率低.
优点:
代码简洁、清晰,易懂
大致意思就是使用递归函数,简单粗暴,清晰易懂,就是效率低,一般数组大小较小时可以使用

作业总结(区间DP)

递归其实经常发挥重要作用,就比如L题(题目网址https://vjudge.net/contest/434302
这个题挺有意思,在A选数的同时,他不仅要想自己怎么取得最大值,还要想着怎么让对方取得最小值,比如一组数据1 2 -3 -4 5 6,输出结果为7,为什么?可能会说A最先取5和6,B只能取1 2 -3 -4那这样输出应该是15才对啊,对,是没错,可B也不傻啊,我选1 2了我还选-3 -4那多亏啊,B得想办法让A少获得点分数,所以他也需要想办法,于是他想到了先拿1 2 -3剩下一个-4给A这样A获得的分数就最小化了,所以这样输出的结果为7,所以就需要找到一组数中的从左边开始或者从右边开始的最小值,这个题我使用了递归函数,第一次发现递归还是CF的一道题,要用到最大公因数
函数代码:

int gcd(int a,int b)
{ return b==0?a:gcd(b,a%b); }

那时我就偶然认识到了递归函数,其实之前递归函数在程序设计课堂上学到过,不过是学的递归的概念,并没有运用到实际的代码中,在区间dp的作业中,我发现大多数都可以使用递归函数来解决问题
思路:定义四个数组,dp数组主要用于判断,d数组用于输出,a数组用于输入,ans数组用于计算前n项和,
AC代码,m表示在一个数组中可以取到的和最小的数组,但是maxx函数是用来求最大数组的,所以函数返回值需要返回d[i][j]=ans[j]-ans[i-1]-m;而最后出只需要输出2*maxx(1,n)-ans[n]即表示相差的值
AC代码:

#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;
int dp[105][105],ans[105],a[105],d[105][105],n;
int maxx(int i, int j)
{
	if (dp[i][j]!=0) return d[i][j];
	dp[i][j]=1;
	int m=0;
	for(int k=i+1;k<=j;k++)
     m=min(m,maxx(k,j));
	for(int k=i;k<j;k++)
		    m=min(m,maxx(i,k));
	d[i][j]=ans[j]-ans[i-1]-m;
	return d[i][j];
}
int main()
{
	 while(cin>>n&&n!=0)
{
 ans[0]=0;
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=n;i++)
{
			cin>>a[i];
			ans[i]=ans[i-1]+a[i];
		}
		cout<<2*maxx(1,n)-ans[n]<<endl;
	}	
	return 0;
}

A.
思路:穿衣服最少件数的问题,定义dp数组,先给dp数组赋初值,按照每次派对要穿的衣服都不一样的情况赋予初值,即

dp[i][j]=j-i+1;

然后使用二重循环对dp进行赋值,然后运行语句

dp[i][j]=dp[i+1][j]+1;
				for(int k=i;k<=j;k++)
					if(a[k]==a[i])
						dp[i][j]=min(dp[i][j],dp[i+1][k-1]+dp[k][j]);

这段语句可能乍一看不太明白,但如果加了注释就非常好懂了,是把每一次循环默认为需要穿一件衣服,也就是衣服件数需要加一,然后再使用循环判断前后聚会是否有需要穿相同衣服的情况,如果有,则取两种情况所需衣服的最小值,即情况一多拿一件衣服和情况二脱掉所需的这件衣服以外的所有衣服(这件衣服不能脱掉)
AC代码:

#include <iostream>
#include <string.h>
#include <algorithm>
using namespace std;
int a[105];
int dp[105][105];
int main() 
{
	int t,casee=1;
	cin>>t;
	while(t--)
{
  int n;
		 cin>>n;
		for(int i=1;i<=n;i++) 
			    cin>>a[i];
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=n;i++)
			for(int j=i;j<=n;j++)
				dp[i][j]=j-i+1;
		for(int i=n-1;i>=1;i--)
			for(int j=i+1;j<=n;j++)
{
				dp[i][j]=dp[i+1][j]+1;
				for(int k=i;k<=j;k++)
					if(a[k]==a[i])
						dp[i][j]=min(dp[i][j],dp[i+1][k-1]+dp[k][j]);
}

		cout<<"Case "<<casee++<<": "<<dp[1][n]<<endl;
	}
	return 0;
}

B.
思路:B题的思路和A题非常相似,双重循环的方式和A相同,并且每一次循环的内也进行一次判断语句,判断是否存在配对的情况,如果有,则运行语句

dp[i][j]=max(dp[i][j],dp[i+1][j-1]+2);

即上一个区间的配对数和该区间的配对数取最大值,并运行一次循环语句,把区间再分为多组两个区间,取其最大配对数,相对于A感觉B更简单些

#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;
char a[105];
int dp[105][105];
int main()
{
    while(cin>>a&&a[0]!='e')
    {
        memset(dp,0,sizeof(dp));
        int l;
        l=strlen(a);
        for(int i=l-1;i>=0;i--)
        {
            for(int j=i+1;j<=l-1;j++)
            {
                if(((a[i]=='(')&&(a[j]==')'))||((a[i]=='[')&&(a[j]==']')))
                    dp[i][j]=max(dp[i][j],dp[i+1][j-1]+2);
                for(int k=i;k<=j;k++)
                {
                    dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]);
                }
            }
        }
        cout<<dp[0][l-1]<<endl;
    }
    return 0;
}

C.
做C题的时候还没没忍住使用了递归,还是感觉递归实在是太好用了,并且从这题发现递归和区间dp有着较大的相似,区间dp是将每个区间的值算出,并且求出其最值,而递归函数同样是在函数体内将每一个区间的dp值求出,然后求出大区间的dp值,而且返回值的可操作性更大
思路:递归函数体求的是一个大区间内的最小值,而函数体内是用每一个小区间的最小值找出大区间的最小值,最后返回结果
注意:在该题中,使用递归函数需要先判断该dp值是否被操作,如果已经被操作则直接返回该值,再判断区间长度,如果区间长度小于等于2,则直接返回0,运行完毕这两个判断语句之后才可运行求区间最小值的语句,最后返回所操作的区间的dp值

#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;
#define MAX 0x3f3f3f3f
int num[105];
int dp[105][105];
int minx(int a,int b)
{
    if(dp[a][b]!=MAX) return dp[a][b];
    if(a==b-1) return dp[a][b]=0;
    for(int i=b-1;i>=a+1;i--)
        dp[a][b]=min(dp[a][b],minx(a,i)+minx(i,b)+num[i]*num[a]*num[b]);
    return dp[a][b];
}
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>num[i];
    memset(dp,MAX,sizeof(dp));
    cout<<minx(1,n);
    return 0;
}

Q.
该题我也是使用递归进行的解题,暂时没想到使用一个递归函数求解的方法,就使用了两个递归函数,幸运的是没有超时,代码可以AC,相比于之前的递归代码,这个也不算太难,就是需要注意一点,就是两个递归代码返回本身的判断语句不太一样,因为一个是求最小值,对求最小值的递归函数,如果已经对该dp进行了操作,而且对dp的值进行了修改,就说明该区间的dp值已经得到了正确的操作,这时只要返回该区间的dp就可以了,如果不是的话,则为dp赋值为一个极大值,然后运行函数体内的主要语句,因为有min函数,需要返回最小值,而返回最大值的函数则相反,就需要赋值为-1
AC代码:

#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;
int minn(int i,int j);
int maxx(int i,int j);
int a[111],sum[111],dp1[111][111],dp2[111][111]; 
int main()
{
	int n,i;
	while(cin>>n)
	{memset(dp1,0,sizeof(dp1));
 memset(dp2,0,sizeof(dp2));
 memset(sum,0,sizeof(sum));
	for(i=1;i<=n;i++)
		{
		cin>>a[i];
		sum[i]=sum[i-1]+a[i];			
		}
	cout<<minn(1,n)<<" "<<maxx(1,n)<<endl;}
}
int minn(int i,int j)
{
	int k;
	if(dp2[i][j]&&dp2[i][j]!=999999)	return dp2[i][j];
	dp2[i][j]=999999;
	if(i==j)	return 0;
	for(k=i;k<j;k++)
		dp2[i][j]=min(dp2[i][j],minn(i,k)+minn(k+1,j)+sum[j]-sum[i-1]);
	return dp2[i][j];	
}
int maxx(int i,int j)
{
	int k;
	if(dp1[i][j]&&dp1[i][j]!=-1)	return dp1[i][j];
	dp1[i][j]=-1;
	if(i==j)	return 0;
	for(k=i;k<j;k++)
		dp1[i][j]=max(dp1[i][j],maxx(i,k)+maxx(k+1,j)+sum[j]-sum[i-1]);
	return dp1[i][j];	
}

感悟:
感觉递归函数和区间dp非常相似,只是递归函数是新定义的一个函数,而区间dp更偏向于寻找较复杂的动态转移方程,在主函数内直接运行,并输出结果,总体感觉递归比普通的区间dp好一点,稍微总结了一下递归函数使用时需注意的两点:
1.递归函数主要语句要写到后面,前面写不运行函数体主要语句的情况,并且返回特定值
2.递归函数也是求出每一个区间特定的值,在定义数组时一定要定义为全局变量
区间dp比线性dp更难找状态转移方程,与线性dp的思想大致相同,线性dp是找到每一个的值,而区间dp是找到每一个区间的值,难度比线性dp难一点,但是如果使用递归函数,自我感觉难度比线性dp要小一点,虽然递归使用的脑子比普通的区间dp要少,但是递归使用的空间比普通的区间dp更大,果然是鱼和熊掌不可兼得啊!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值