网络知识 娱乐 2021蓝桥杯国赛C/C++B组真题

2021蓝桥杯国赛C/C++B组真题

答案:2653631372 

#include
#include
#define ll long long
using namespace std;
ll dp[2022];
int main()
{
    memset(dp,0x7f,sizeof(dp));//初始化最大值(7F是127);
    dp[0]=0;//根据题意,当子树为空时权值为0;
    //dp[i]代表有i个结点来组成这棵树 
    for(int i=1;i<=2021;i++)//自下而上;
        for(int j=0;j<i;j++)	//j是枚举所有左子树中结点的个数 
        	dp[i]=min(dp[i],1+2*dp[j]+3*dp[i-1-j]+j*j*(i-1-j));
    cout<<dp[2021]<<endl;
    return 0;
}

【解析】

  知识点----动态规划(树形dp?)

注意事项:动态规划永远要记得给某些子状态进行初始化!注意在循环时从几开始

 是求最小权值,下图里写错了!!!

 

【解析】

法I a的ASCII是97,A是65,所以可以遍历字符串把每一个>=97的值都-32输出

一开始自己写的代码:用的是ASCII码 

#include
#include
#include
using namespace std;

//a~97 A~65 

int main(){
	string myS;
	cin>>myS;
	for(int i=0;i=97)
		myS[i]-=32;
	}
	cout<<myS;
}

法II使用库函数 

然后发现还有一个toupper() 函数, int toupper(int c) ,用来将小写字母转换为大写字母。 只有当参数 c 是一个小写字母,并且存在对应的大写字母时,这种转换才会发生。

需要#include

#include
#include
#include
#include
using namespace std;

//a~97 A~65 

int main(){
	char c;
	while(cin>>c){
		cout<<(char)toupper(c);
	}
	return 0;
}

如图,小写转大写的效果

同理,那么大写转小写也有一个库函数那就是tolower()

一开始都没有看出这两个就是toUppertoLower的拼写。。。 

 【解析】

首先观察这个数列,第一大项是1~1,第二大项是1~2,第n大项就是1~n

题目要求给出左右端点就能算出这一段的和是多少

如何求区间之和呢?用前缀和算,然后右端点和左端点-1的前缀和做个差就行

前缀和怎么算呢?题解是找到规律,我也没想到更合适的法。。

我们现在找到规律后只能算出按规律分块时,到每一块的前缀和,但是询问的区间可能会包含下一个块的一部分。因此我们用二分确定前面有多少个完整的块,就能算出这些块的和S1。后面不完全的从1开始的数用一共有n-(前面分块数)个,用等差数列再算后边这一坨的和S2,答案就是S2+S1

  二分代码不太明白,多理解几遍

#include

using namespace std;

typedef long long ll;

//到第n 块 的前缀和 
ll cal1(ll n){
	return n*(n+1)*(n+2)/6;
}

//在某一个从1开始的块中,前n个数的和 
//或者是求从第1个块开始到第n块一个有多少个数 
ll cal2(ll n){
	return n*(n+1)/2;
} 

// 二分 查看第n个数前面有多少个完整的块
ll getSeg(ll n){
	ll l=1,r=1e9,mid,ans;
	while(l<=r){
		mid=l+(r-l)/2;
	    //块数少了,加起来到不了第n个数 
		if(cal2(mid)>T;
	while(T--){
		cin>>l>>r;
		cout<<solve(r)-solve(l-1)<<endl;
	}
	return 0;
}

 【思路】

显然这是一道经典的数位dp

数位dp一般用于统计一个区间内满足一些条件的个数。

数位dp的实质就是换一种暴力枚举的方式,使得新的枚举方式满足dp的性质

然后记忆化就可以了

如果我们想求一个区间内满足条件的个数,最简单的暴力是

for(int i=l;i<=r;i++)
    if(check(i)) ans++;

这太暴力了,毫无状态可言,也毫无记忆化可言,要优化。

新的枚举是控制上界枚举,从最高位开始往下枚举

因此,main函数这样写

int main(){
	long long l,r;
	while(cin>>l>>r)
		cout<<( solve(r)-solve(l-1) );
}

 数位dp的模板

typedef long long ll;
int a[20];
ll dp[20][state]; //不同题目状态不同,前面代表当前位置,后面代表状态

ll dfs(int pos,bool lead,bool limit){
	//参数有state变量,前导零,数位上界变量
	if(pos==-1) return 1; //这是递归的边界,因为按位枚举最低位是0,因此pos==-1代表这个数已经 被枚举完了
	//一般来说是返回1,表示枚举的这个数是合法的(前边也都是合法的) 
	
	//之后就是进行记忆化(在此之前,根据题目的不同,可能还要剪枝)
	 if(!limit && !lead && dp[pos][state]!=-1) return dp[pos][state];
	  
	  int up=limit?a[pos]:9; //判断枚举的上界up
	  ll ans=0;
	  
	  //开始计数 
	  for(int i=0;i>l>>r){
		//初始化dp数组为-1
		cout<<solve(r)-solve(l-1); 
	}
}

存疑的地方:为什么记忆化的时候必须要是!limit

举例说明:当前题目的约束是数位上不能出现两个连续的1,我们把区间设为[1,210]

 状态:dp[pos][pre]代表枚举到当前pos位,前面一位是pre,的个数

假如,我们不判limit,直接记忆化:那么假设我们第一次枚举了百位是0,显然后面的枚举limit=false,也就是数位上0到9的枚举,然后当我十位枚举了1,此时考虑dp[0][1],就是枚举到个位,前一位是1的个数,显然dp[0][1]=9;(个位只有是1的时候是不满足的),这个状态记录下来,继续dfs,一直到百位枚举了2,十位枚举了1,显然此时递归到了pos=0,pre=1的层,而dp[0][1]的状态已经有了即dp[pos][pre]!=-1;此时程序直接return dp[0][1]了,然而显然是错的,因为此时是有limit的个位只能枚举0,根本没有9个数,这就是状态冲突了。有lead的时候可能出现冲突,这只是两个最基本的不同的题目可能还要加限制,反正宗旨都是让dp状态唯一。

总结一下,要注意的就是前导0出现在两个地方,和与它在那些地方一同出现的limit写法差不多

遇到不符合约束条件的就continue

其次就是solve函数写法记一下。

另外,在调用solve之前,把dp数组初始化为-1

最重要的是这个dp数组的第2维到底放什么(不要62放的是前一位是不是6,windy数放的是前一位的值,这道题放的是当前1的个数)

#include
#include
#include
using namespace std;
typedef long long ll; 

ll dp[105][55];
int limit[105];
int k;

ll dfs(int pos,int sum,int limit){
	if(pos==-1) return sum==k;//这个return sum==k就很巧妙,如果满足1的个数,才会返回1(而不是像模板一样不管啥,只要枚举完所有就返回1) 
	if(!limit && dp[pos][sum]!=-1)
		return dp[pos][sum];
	int up=limit?a[pos]:1;
	ll ans=0;
	for(int i=0;i>n>>k;
	cout<<solve(n);
	return 0;
}