程序设计思维与实践 Week10 Blog

一、大纲

本周作业与实验题目如下:

  • 签到题1
  • 东东转魔方(纯模拟)
  • 签到题2
  • LIS&LCS(动态规划)
  • 拿数问题II(动态规划)

二、逐个击破

1.签到题1

题目描述

  东东有一个字符串X,该串包含偶数个字符,一半是 S 字符,一半是 T 字符
  东东可以对该字符串执行 1010000 次操作:如果存在 ST 是该串的子串,则删除掉最左边的 ST。
  即 TSTTSS⇒TTSS、SSSTTT⇒SSTT⇒ST⇒空

  1. Input

  (2 ≦ |X| ≦ 200,000)

  1. Output

  输出最终串的长度

题目分析

  这道题目的关键在于删除一些ST后如何往前回溯时定位到哪些是没有删除的,因为这里的删除不可能真正涉及字符串的删除那样复杂度太高,只需要合理的记录,这里采用的方法是初始化时定义一个 preprepre 数组用于记录位于第 iii 个索引的数值前面的一个数的索引为 i−1i-1i1 ,然后依次判断,如果发生了删除就更新pre数组定位到删除数值前面的索引,这样就可以实现O(1)O(1)O(1)复杂度查找“前面”的数了。
综上所述,题目的全部代码如下:

#include<iostream>
#include<cstring>
using namespace std;
const int N = 2*1e5+10;
char str[N];
int pre[N];
int r,l,length, ans;
bool jud;

int main()
{
    scanf("%s",str);
    length = strlen(str);
    for(int i = 0; i < length; i++)pre[i] = i - 1;
    r = 1;
    while(r < length)
    {
        jud = false;
        while(str[l]=='S' && str[r]=='T')
        {
            ans += 2;
            if(pre[l] < 0 || r + 1 > length)
            {
                jud = true;
                break;
            }
            l = pre[l];
            r++;
            pre[r] = l;
        }
        if(pre[l] < 0 && jud)l = r + 1, r += 2;
        else l = r, r++;
    }
    printf("%d",length - ans);
    return 0;
}

2.东东学打牌

题目描述

  东东有一个二阶魔方,即2×2×2的一个立方体组。立方体由八个角组成。

  • 魔方的每一块都用三维坐标(h, k, l)标记,其中h, k, l∈{0,1}。六个面的每一个都有四个小面,每个小面都有一个正整数。
  • 对于每一步,东东可以选择一个特定的面,并把此面顺时针或逆时针转90度。
  • 请你判断,是否东东可以在一个步骤还原这个魔方(每个面没有异色)。
  1. Input

  输入的第一行包含一个整数N(N≤30),这是测试用例的数量。
  对于每个测试用例, 第 1~4 个数描述魔方的顶面,这是常见的2×2面,由(0,0,1),(0,1,1),(1,0,1),(1,1,1)标记。四个整数对应于上述部分。
  第 5~8 个数描述前面,即(1,0,1),(1,1,1),(1,0,0),(1,1,0)的公共面。四个整数 与上述各部分相对应。
  第 9~12 个数描述底面,即(1,0,0),(1,1,0),(0,0,0),(0,1,0)的公共面。四个整数与上述各部分相对应。
  第 13~16 个数描述背面,即(0,0,0),(0,1,0),(0,0,1),(0,1),(0,1,1)的公共面。四个整数与上述各部分相对应
  第 17~20 个数描述左面,即(0,0,0),(0,0,1),(1,0,0),(1,0,1)的公共面。给出四个整数与上述各部分相对应。
  第 21~24 个数描述了右面,即(0,1,1),(0,1,0),(1,1,1),(1,1,0)的公共面。给出四个整数与上述各部分相对应。
  换句话说,每个测试用例包含24个整数a、b、c到x。你可以展开表面以获得平面图
  如下所示。
在这里插入图片描述

  1. Output

  对于每个测试用例,魔方如果可以至多 “只转一步” 恢复,输出YES,则输出NO。

题目分析

  这个题目一开始想要通过一些比较规律性的方法来解决,但是并没有什么头绪,而且看了看数据规模非常小而且情况并不是十分的复杂所以就采取了纯模拟出魔方旋转的情况,总共有三种情况,分别是:

  • 魔方的上面顺时针、逆时针旋转90度
  • 魔方的左面顺时针、逆时针旋转90度
  • 魔方的前面顺时针、逆时针旋转90度

  (这里注意上、左、前面的旋转就包括了下、右、后面的旋转,不要傻傻的模拟了6次)最后不要忘记判断在书写主函数时旋转之前是否满足已经复原的情况然后再进行旋转。

  该题全部解决代码如下:

#include<iostream>
#include<algorithm>
using namespace std;
int a[25];

bool above()
{//转上面相当于转底面

	if((a[0]==a[1]&&a[1]==a[2]&&a[2]==a[3]) && (a[8]==a[9]&&a[9]==a[10]&&a[10]==a[11]))
	{	//首先是上面顺时针旋转
		if((a[6]==a[7]&&a[7]==a[20]&&a[20]==a[22]) && (a[4]==a[5]&&a[5]==a[16]&&a[16]==a[18])
		&& (a[17]==a[19]&&a[19]==a[12]&&a[12]==a[13]) && (a[14]==a[15]&&a[15]==a[21]&&a[21]==a[23])) return true;
		//然后是上面逆时针旋转 
		else if((a[4]==a[5]&&a[5]==a[21]&&a[21]==a[23]) && (a[6]==a[7]&&a[7]==a[17]&&a[17]==a[19])
		&& (a[16]==a[18]&&a[18]==a[14]&&a[14]==a[15]) && (a[12]==a[13]&&a[13]==a[20]&&a[20]==a[22])) return true;
	}
	return false;	
}

bool front()
{//转前面相当于转后面

	if((a[4]==a[5]&&a[5]==a[6]&&a[6]==a[7]) && (a[12]==a[13]&&a[13]==a[14]&&a[14]==a[15]))
	{	//首先是前面顺时针旋转
		if((a[8]==a[9]&&a[9]==a[16]&&a[16]==a[17]) && (a[18]==a[19]&&a[19]==a[0]&&a[0]==a[1])
		&& (a[2]==a[3]&&a[3]==a[20]&&a[20]==a[21]) && (a[22]==a[23]&&a[23]==a[10]&&a[10]==a[11])) return true;
		//然后是前面逆时针旋转 
		else if((a[8]==a[9]&&a[9]==a[20]&&a[20]==a[21]) && (a[18]==a[19]&&a[19]==a[10]&&a[10]==a[11])
		&& (a[2]==a[3]&&a[3]==a[16]&&a[16]==a[17]) && (a[22]==a[23]&&a[23]==a[0]&&a[0]==a[1])) return true;
	}
	return false;	
}

bool left()
{//转左面相当于转右面

	if((a[16]==a[17]&&a[17]==a[18]&&a[18]==a[19]) && (a[20]==a[21]&&a[21]==a[22]&&a[22]==a[23]))
	{	//首先是左面顺时针旋转
		if((a[4]==a[6]&&a[6]==a[9]&&a[9]==a[11]) && (a[8]==a[10]&&a[10]==a[13]&&a[13]==a[15])
		&& (a[12]==a[14]&&a[14]==a[1]&&a[1]==a[3]) && (a[0]==a[2]&&a[2]==a[5]&&a[5]==a[7])) return true;
		//然后是左面逆时针旋转 
		else if((a[4]==a[6]&&a[6]==a[1]&&a[1]==a[3]) && (a[0]==a[2]&&a[2]==a[13]&&a[13]==a[15])
		&& (a[12]==a[14]&&a[14]==a[9]&&a[9]==a[11]) && (a[8]==a[10]&&a[10]==a[5]&&a[5]==a[7])) return true;
	}
	return false;	
}



int main()
{
	int N;
	scanf("%d",&N);
	while(N--)
	{
		for(int i=0;i<24;i++) scanf("%d",&a[i]);
		if((a[0]==a[1]&&a[1]==a[2]&&a[2]==a[3]) && (a[4]==a[5]&&a[5]==a[6]&&a[6]==a[7])
		&& (a[8]==a[9]&&a[9]==a[10]&&a[10]==a[11]) && (a[12]==a[13]&&a[13]==a[14]&&a[14]==a[15])
		&& (a[16]==a[17]&&a[17]==a[18]&&a[18]==a[19]) && (a[20]==a[21]&&a[21]==a[22]&&a[22]==a[23]))
			printf("YES\n");
		else if(above() || front() || left())
		{
			if(above()) printf("YES\n");
			else if(front()) printf("YES\n");
			else printf("YES\n");
		} 
			
		else printf("NO\n");			 
	}
	return 0;
 } 

  Tip: 这里使用的模拟方法还是略显笨重,出现了大量的相似代码的重复,其实可以使用循环来简化代码风格,这个在以后的考试中也要注意,而且重复相似的1代码在粘贴使用时容易遗漏细节而且难以发现

3.签到题2

题目描述

  在一开始他有一个数字n,他的目标是把它转换成m,在每一步操作中,他可以将n乘以2或乘以3,他可以进行任意次操作。输出将n转换成m的操作次数,如果转换不了输出-1。

  1. Input

  输入的唯一一行包括两个整数n和m(1<=n<=m<=5*10^8).

  1. Output

  输出从n转换到m的操作次数,否则输出-1.

题目分析

   这道题目较为简单,因为2与3是互质的两个数,所以直接将m对n取余,如果不能够整除则说明永远无法转化成功,然后不断根据 Mmod2M mod2Mmod2 是否等于0,以及 Mmod3M mod3Mmod3 是否等于0,然后根据对应的情况对可以整除的不断缩小倍数直至为1即得到答案,如果最后无法缩小至1则说明页无法转化成功,输出-1即可。

   所以这道题目具体的代码如下:

#include<iostream>
using namespace std;
int n,m,cnt;

int main()
{
	scanf("%d%d",&n,&m);
	if(m%n != 0)
	{
		printf("-1"); 
		return 0;
	}
	m/=n;
	while(m!=1)
	{
		if(m%2==0)
		{
			m/=2;
			cnt++;
		}
		else if(m%3==0)
		{
			m/=3;
			cnt++;
		}
		else 
		{
			printf("-1");
			return 0;
		}
	}
	printf("%d",cnt);
	return 0;
}

4.LIS&LCS

题目描述

  东东有两个序列A和B。
  他想要知道序列A的LIS和序列AB的LCS的长度。
  注意,LIS为严格递增的,即a1<a2<…<ak(ai<=1,000,000,000)。

  1. Input

  第一行两个数n,m(1<=n<=5,000,1<=m<=5,000)
  第二行n个数,表示序列A
  第三行m个数,表示序列B

  1. Output

  输出一行数据ans1和ans2,分别代表序列A的LIS和序列AB的LCS的长度

题目分析

   首先进行两个概念的说明:子序列子串

  • 子序列指的是一个完整序列中可以非连续、并且按顺序取得的子集。
    例如:1,6,2,3,7,51,6,2,3,7,5162375 的序列,其子序列可以为 1,2,71,2,71,2,7
  • 子串指的是一个完整序列中必须连续、并且按顺序取得的子集。
    例如:1,6,2,3,7,51,6,2,3,7,5162375 的序列,其子串可以为 1,6,21,6,21,6,2

  这样这道题目所求的就是最长上升子序列(LIS)和最长公共子序列(LCS),这两种求解的方式都是是用动态规划,而且对于子序列类型的问题解决最常用的方法就是动态规划
  求解LIS具体相关定义如下:

  • 状态定义:fif_ifi 表示以 AiA_iAi 为结尾的最长上升序列的方程。
  • 初始化:f1=1f_1=1f1=1
  • 转移过程:fi=max(fj∣j<i∧Aj<Ai)+1f_i=max(f_j| j<i\wedge A_j<A_i)+1fi=max(fjj<iAj<Ai)+1
  • 输出答案:max(fi),i=1,2,...,nmax(f_i),i=1,2,...,nmax(fi),i=1,2,...,n
  • 时间复杂度:O(n2)O(n^2)O(n2)

  求解LCS具体相关定义如下:

  • 状态定义: f[i][j]f[i][j]f[i][j]A1,A2,…,AiA1, A2, …, AiA1,A2,,AiB1,B2,…,BjB1, B2, …, BjB1,B2,,Bj 的 LCS 长度
  • 初始化:f[1][0]=f[0][1]=f[0][0]=0f[1][0] = f[0][1] = f[0][0] = 0f[1][0]=f[0][1]=f[0][0]=0
  • 转移过程:Ai==BjAi == BjAi==Bj 时,f[i][j]=f[i−1][j−1]+1f[i][j] = f[i-1][j-1] + 1f[i][j]=f[i1][j1]+1
    否则f[i][j]=max(f[i−1][j],f[i][j−1])f[i][j] = max(f[i-1][j] ,f[i][j-1])f[i][j]=max(f[i1][j],f[i][j1])
  • 输出答案:f[n][m]f[n][m]f[n][m]
  • 时间复杂度:O(nm)O(nm)O(nm)

   所以这道题目具体的代码如下:

#include<iostream>
#include<algorithm>
const int N = 5005;
using namespace std;
int n,m,ans_lis,ans_lcs;
int A[N],B[N],f[N],L[N][N];

int LIS()
{//求取A序列的LIS长度
	for(int i=1;i<=n;i++)f[i]=1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=i;j++)
			if(A[i]>A[j])
				f[i]=max(f[i],f[j]+1);
	
	int tmp_max=-1;
	for(int i=1;i<=n;i++)
		if(f[i]>tmp_max)tmp_max=f[i];
	return tmp_max;
}

int LCS()
{
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			if(A[i]==B[j])L[i][j]=L[i-1][j-1]+1;
			else L[i][j]=max(L[i-1][j],L[i][j-1]);
		}
	return L[n][m];
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&A[i]);
	for(int i=1;i<=m;i++) scanf("%d",&B[i]);
	ans_lis = LIS();
	ans_lcs = LCS();
	printf("%d %d",ans_lis,ans_lcs);	
	return 0;
}

5.选数问题II

题目描述

  给一个序列,里边有 n 个数,每一步能拿走一个数,比如拿第 i 个数, Ai = x,得到相应的分数 x,但拿掉这个 Ai 后,x+1 和 x-1 (如果有 Aj = x+1 或 Aj = x-1 存在) 就会变得不可拿(但是有 Aj = x 的话可以继续拿这个 x)。求最大分数。

  1. Input

  第一行包含一个整数 n(1 ≤ n ≤ 105)n (1 ≤ n ≤ 10^5)n(1n105),表示数字里的元素的个数
  第二行包含n个整数 a1,a2,...,an(1 ≤ ai ≤ 105)a1, a2, ..., an (1 ≤ ai ≤ 10^5)a1,a2,...,an(1ai105)

  1. Output

  输出一个整数:n你能得到最大分值。

题目分析

   这道题目和传统的那种拿数问题有一些小小的区别,传统的拿数问题表述为若是拿了A[i]那么A[i+1]和A[i-1] 就不可拿,动态转移方程是 dp[i]=max(dp[i−1],dp[i−2]+A[i])dp[i] = max(dp[i-1], dp[i-2]+A[i])dp[i]=max(dp[i1],dp[i2]+A[i]) ,但是这道题描述为拿了A[i],则A[i]+1和A[i]-1就不可以拿,所以这里采用的定义和转移方程如下:

  • 状态定义:dp[i]dp[i]dp[i] 表示考虑 sum[1..i]sum[1..i]sum[1..i] 可以拿到的最大分数。
    sum[num]sum[num]sum[num] 定义为数为num的总和
  • 初始化:dp[1]=sum[1]dp[1]=sum[1]dp[1]=sum[1]
  • 转移过程:dp[i]=max(dp[i−1],dp[i−2]+sum[i])dp[i]=max(dp[i-1],dp[i-2]+sum[i])dp[i]=max(dp[i1],dp[i2]+sum[i])
  • 输出答案:dp[tmpmax]dp[tmp_{max}]dp[tmpmax],这里tmpmaxtmp_{max}tmpmax 定义为所有aia_iai的最大值

   所以这道题目具体的代码如下:

#include<iostream>
#define ll long long
const int N = 1e5+5;
using namespace std;
ll n,tmp_max;
ll dp[N],sum[N];

int main()
{
	scanf("%lld",&n);
	ll num;
	for(ll i=1;i<=n;i++)
	{
		scanf("%lld",&num);
		sum[num]+=num;
		tmp_max = max(tmp_max,num);
	 } 
	dp[1]=sum[1];//一定注意这个地方的处理 
	for(ll i=2;i<=tmp_max;i++)
		dp[i]=max(dp[i-1],dp[i-2]+sum[i]);
	printf("%lld",dp[tmp_max]);	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值