5.14 赛后总结

为神马JQ比我先发啊(QAQ)

本人蒟蒻一枚,帖子中如有错误,请在评论区指正

500分考了270,白给了75,不玩了玩不起

做个简单的总结吧。

对了,除了第 3 3 3 题,没有现成代码,不要指望复制粘贴。

T1零钱兑换

考点:完全背包

为什么这么说呢?

你可以认为每种硬币的数量是无限的。

以此,我们可以判断出,这是一道完全背包。

所以,可以定义:

d p [   i   ] dp[\ i\ ] dp[ i ] 表示凑出 i i i 的面值最少需要的硬币数量。

所以,我们不难推出:

dp[i]=min(dp[i],dp[i-a[j]]+1);
//显然,dp[i-a[j]]表示凑出i-a[j]的面值最少需要的硬币数量,再加上一个面值为a[j]的硬币即可凑出i的面值。

当然,只有满足 i ≥ a [   j   ] i \geq a[\ j\ ] ia[ j ]时,上述状态转移方程用。

所以,在完成输入后,可以对 a a a 数组进行排序,方便后面判断,当 i < a [   j   ] i < a[\ j\ ] i<a[ j ] ,就直接退出循环。

关于我犯的一个愚蠢错误:

题目已知:

0 ≤ a m o u n t ≤ 1 0 4 0 \leq amount \leq 10^4 0amount104

可是当我在定义数组时:

int dp[1005];
//1000=10^3

RE,白给75。

所以,检查真的很重要

简单的说说检查要检查些什么:

  1. freopen 及里面的文件名
  2. 源程序文件名
  3. 数组定义够不够用
  4. 程序中间测试有的有没有注释掉

T2蝙蝠侠的麻烦

考点:最长公共子序列(以下简称LCS)

这个考点应该是显然的。

但是,与常规的LCS相比,这道题要求的是 3 3 3 个序列的LCS。

其实类比常规的LCS,这题也没啥难的。

定义状态:

d p [   i   ] [   j   ] [   k   ] dp[\ i\ ][\ j\ ][\ k\ ] dp[ i ][ j ][ k ] 表示第 1 1 1 个序列的前 i i i 个字符,第 2 2 2 个序列的前 j j j 个字符,第 3 3 3 个序列的前 k k k 个字符的LCS。

显然,当 a [   i   ] = b [   j   ] = c [   k   ] a[\ i\ ]=b[\ j\ ]=c[\ k\ ] a[ i ]=b[ j ]=c[ k ] 时, a [   i   ] a[\ i\ ] a[ i ] b [   j   ] b[\ j\ ] b[ j ] c [   k   ] c[\ k\ ] c[ k ] 都可以为答案作出贡献,所以这个时候,就可以将 d p [   i − 1   ] [   j − 1   ] [   k − 1   ] + 1 dp[\ i-1\ ][\ j-1\ ][\ k-1\ ]+1 dp[ i1 ][ j1 ][ k1 ]+1 的值赋给 d p [   i   ] [   j   ] [   k   ] dp[\ i\ ][\ j\ ][\ k\ ] dp[ i ][ j ][ k ]

反之,说明 a [   i   ] a[\ i\ ] a[ i ] b [   j   ] b[\ j\ ] b[ j ] c [   k   ] c[\ k\ ] c[ k ] 中,至少有 1 1 1 个无法为答案作出贡献,所以,取 d p [   i − 1   ] [   j   ] [   k   ] dp[\ i-1\ ][\ j\ ][\ k\ ] dp[ i1 ][ j ][ k ] d p [   i   ] [   j − 1   ] [   k   ] dp[\ i\ ][\ j-1\ ][\ k\ ] dp[ i ][ j1 ][ k ] d p [   i   ] [   j   ] [   k − 1   ] dp[\ i\ ][\ j\ ][\ k-1\ ] dp[ i ][ j ][ k1 ] 中的最大值,将其赋给 d p [   i   ] [   j   ] [   k   ] dp[\ i\ ][\ j\ ][\ k\ ] dp[ i ][ j ][ k ]

这题就没什么值得注意的点了,下一道。

T3带分数

考点:DFS + 剪枝

这道题有多种做法,这里展示的是我的做法。(其实是考场上的代码有问题,数据加强后仅90分,改成了官方做法

显然,对于任意一个带分数,将加号和除号去掉后,得到的是1至9的一个全排列

那么我们就有了基本思路:我们可以求出一个全排列,在判断能否将其分割成我们想要的数。具体有一些优化,结合代码来讲。

#include
int a[15],ALL;//ALL用于统计总数量
bool flag[15];
void write(int num){//快读
    if(num<0){
        putchar('-');
        num*=-1;
    }
    if(num>9){
        write(num/10);
    }
    putchar(num%10+'0');
}
int Sum(int l,int r){//将一个指定长度的数组转化成数值
	int ans=0;
	for(int i=r;i>=l;i--){
		ans=ans*10+a[i];
	}
	return ans;
}
void dfs1(int a[],int num){//第二个dfs,用于创造带分数
	int m1,m2,m3;
	for(int i=1;i<=7;i++){//枚举带分数中整数部分的截止位置
	                      //关于截止位置:整数部分的值为序列[1,i]转化成的数值的值
		                  //因为分子和分母至少要有一位,所以最多到7截止
		m1=Sum(1,i);//赋值
		if(m1>num){//第一个剪枝:如果带分数中整数部分已经大于等于我们要求得的数
			return ;//直接截止
		}
		for(int k=i+(9-i)/2;k<=8;k++){//枚举分母的截止位置。截止位置含义同上
		//第二个剪枝
		//分子分母会在序列[i+1,9]中排出,而分母必须大于分子,即分母长度必须大于等于分子长度
		//所以,分母长度为(9-i)/2,在加上i,就是分母最小的截止位置
		//因为分子至少要有一位,所以最多到8截止
			m2=Sum(i+1,k);
			m3=Sum(k+1,9);
			if(m2>m3&&m2%m3==0&&m1+m2/m3==num){
				ALL++;
				write(num);
				putchar('=');
				write(m1);
				putchar('+');
				write(m2);
				putchar('/');
				write(m3);
				putchar('\n');
				//以上输出相当于printf("%d=%d+%d/%d\n",num,m1,m2,m3);
			}
		}
	}
}
void dfs(int num,int n){//第一个dfs,造出全排列
	if(num==10){//全排列造好了
		dfs1(a,n);
	}
	for(int i=1;i<=9;i++){
		if(flag[i]==0){
			flag[i]=1;
			a[num]=i;
			dfs(num+1,n);
			flag[i]=0;
			a[num]=0;
		}
	}
}
int main(){
	freopen("fraction.in","r",stdin);
    freopen("fraction.out","w",stdout);
	int n;
	while(scanf("%d",&n)!=EOF){
		ALL=0;
		dfs(1,n);
		printf("%d\n",ALL);
	}
	return 0;
}

其实关于全排列,STL里面有相关的函数,大家可以在here查阅更多资料。

T4锦鲤序列

考点:LIS + 二分

这题很明显是一道LIS的变形。

下面展示蒟蒻的做法:

我们定义:

d p [   i   ] dp[\ i\ ] dp[ i ] 表示序列 [   1 , i   ] [\ 1,i\ ] [ 1,i ] 中长度最长的最长上升子序列的长度(必须包含第 i i i 个元素)。

d p 1 [   i   ] dp1[\ i\ ] dp1[ i ] 表示序列 [   i , n   ] [\ i,n\ ] [ i,n ] 中长度最长的最长下降子序列的长度(必须包含第 i i i 个元素)。

这题就这么简单的完成了……吗?

绝对不可能!

观察 n n n 的取值范围:

1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1n105

数据范围很大, O ( n 2 ) O(n^2) O(n2) 算法是过不去的,至少也要是 O ( n × log ⁡ n ) O(n \times \log_n) O(n×logn)

关于LIS的算法,有一个优化后的算法,其时间复杂度可以从 O ( n 2 ) O(n^2) O(n2) 降到 O ( n × log ⁡ n ) O(n \times \log_n) O(n×logn)

关于这个优化后的算法:

蒟蒻会写,但是解释不清楚

为了不误导更多人,这里不做解释(话说应该都会写吧),大家可以试试这道LIS,只能用优化后的算法才能过。

T5烈焰

考点:递推

话说递推和线性DP有神马区别

本次测试中最难的一道。

让我们先从最难的想的状态定义入手吧:

定义:

d p [   i   ] [   0   ] [   0   ] dp[\ i\ ][\ 0\ ][\ 0\ ] dp[ i ][ 0 ][ 0 ] 表示前 i i i 个格子中,第 i i i 个格子无烈焰,第 i + 1 i+1 i+1 个格子无烈焰的情况总数

d p [   i   ] [   0   ] [   1   ] dp[\ i\ ][\ 0\ ][\ 1\ ] dp[ i ][ 0 ][ 1 ] 表示前 i i i 个格子中,第 i i i 个格子无烈焰,第 i + 1 i+1 i+1 个格子有烈焰的情况总数

d p [   i   ] [   1   ] [   0   ] dp[\ i\ ][\ 1\ ][\ 0\ ] dp[ i ][ 1 ][ 0 ] 表示前 i i i 个格子中,第 i i i 个格子有烈焰,第 i + 1 i+1 i+1 个格子无烈焰的情况总数

d p [   i   ] [   1   ] [   1   ] dp[\ i\ ][\ 1\ ][\ 1\ ] dp[ i ][ 1 ][ 1 ] 表示前 i i i 个格子中,第 i i i 个格子有烈焰,第 i + 1 i+1 i+1 个格子有烈焰的情况总数

为什么这样定义呢?

结合下面的状态转移方程来讲:

  1. 当第 i i i 个格子为 0 时。

当第 i i i 个格子为 0 时,我们只需要处理 d p [ i ] [   0   ] [   0   ] dp[i][\ 0\ ][\ 0\ ] dp[i][ 0 ][ 0 ] 的值即可。

很简单,因为第 i i i 个格子为 0 ,所以第 i − 1 i-1 i1 个格子,第 i i i 个格子和第 i + 1 i+1 i+1 上一定没有烈焰,所以只需将 d p [   i − 1   ] [   0   ] [   0   ] dp[\ i-1\ ][\ 0\ ][\ 0\ ] dp[ i1 ][ 0 ][ 0 ] 的值赋给 d p [   i   ] [   0   ] [   0   ] dp[\ i\ ][\ 0\ ][\ 0\ ] dp[ i ][ 0 ][ 0 ] 即可。

  1. 当第 i i i 个格子为 1 时。

当第 i i i 个格子为 1 时,我们需要处理 d p [   i   ] [   0   ] [   0   ] dp[\ i\ ][\ 0\ ][\ 0\ ] dp[ i ][ 0 ][ 0 ] d p [   i   ] [   0   ] [   1   ] dp[\ i\ ][\ 0\ ][\ 1\ ] dp[ i ][ 0 ][ 1 ]的值。

因为当第 i i i 个格子为 1 时,第 i i i 个格子必然没有烈焰,且第 i + 1 i+1 i+1 个格子和第 i − 1 i-1 i1 个格子会有两种情况:

  1. i + 1 i+1 i+1 个格子有烈焰,第 i − 1 i-1 i1 个格子没有烈焰。
  2. i + 1 i+1 i+1 个格子没有烈焰,第 i − 1 i-1 i1 个格子有烈焰。

考虑第 1 1 1 种情况,在该情况下,第 i − 1 i-1 i1 个格子和第 i i i 个格子都没有烈焰,第 i + 1 i+1 i+1 个格子有烈焰,所以,要将 d p [   i − 1   ] [   0   ] [   0   ] dp[\ i-1\ ][\ 0\ ][\ 0\ ] dp[ i1 ][ 0 ][ 0 ] 的值赋给 d p [   i   ] [   0   ] [   1   ] dp[\ i\ ][\ 0\ ][\ 1\ ] dp[ i ][ 0 ][ 1 ]

考虑第 2 2 2 种情况,在该情况下,第 i − 1 i-1 i1 有烈焰,第 i i i 个格子和第 i + 1 i+1 i+1 个格子都没有烈焰,所以,要将 d p [   i − 1   ] [   1   ] [   0   ] dp[\ i-1\ ][\ 1\ ][\ 0\ ] dp[ i1 ][ 1 ][ 0 ] 的值赋给 d p [   i   ] [   0   ] [   0   ] dp[\ i\ ][\ 0\ ][\ 0\ ] dp[ i ][ 0 ][ 0 ]

  1. 当第 i i i 个格子为 2 时。

当第 i i i 个格子为 2 时,我们只需要处理 d p [   i   ] [   0   ] [   1   ] dp[\ i\ ][\ 0\ ][\ 1\ ] dp[ i ][ 0 ][ 1 ] 的值即可。

因为当第 i i i 个格子为 2 时,第 i i i 个格子必然没有烈焰,且第 i + 1 i+1 i+1 个格子和第 i − 1 i-1 i1 个格子必然有烈焰,所以将 d p [   i − 1   ] [   1   ] [   0   ] dp[\ i-1\ ][\ 1\ ][\ 0\ ] dp[ i1 ][ 1 ][ 0 ] 的值赋给 d p [   i   ] [   0   ] [   1   ] dp[\ i\ ][\ 0\ ][\ 1\ ] dp[ i ][ 0 ][ 1 ] 即可。

  1. 当第 i i i 个格子为 * 时。

当第 i i i 个格子为 * 时,我们需要处理 d p [   i   ] [   1   ] [   0   ] dp[\ i\ ][\ 1\ ][\ 0\ ] dp[ i ][ 1 ][ 0 ] d p [   i   ] [   1   ] [   0   ] dp[\ i\ ][\ 1\ ][\ 0\ ] dp[ i ][ 1 ][ 0 ]的值。

因为当第 i i i 个格子为 * 时,第 i i i 个格子必然有烈焰,但第 i + 1 i+1 i+1 个格子和第 i − 1 i-1 i1 个格子的情况我们都不知道,所以要分情况。

考虑关于 d p [   i   ] [   1   ] [   0   ] dp[\ i\ ][\ 1\ ][\ 0\ ] dp[ i ][ 1 ][ 0 ] 的状态转移方程,在该情况下,因为第 i − 1 i-1 i1 个格子的情况我们不知道,所以将 d p [   i − 1   ] [   0   ] [   1   ] + d p [   i − 1   ] [   1   ] [   1   ] dp[\ i-1\ ][\ 0\ ][\ 1\ ]+dp[\ i-1\ ][\ 1\ ][\ 1\ ] dp[ i1 ][ 0 ][ 1 ]+dp[ i1 ][ 1 ][ 1 ] 取模后的值赋给 d p [   i   ] [   1   ] [   0   ] dp[\ i\ ][\ 1\ ][\ 0\ ] dp[ i ][ 1 ][ 0 ]注意取模

同理, d p [   i   ] [   1   ] [   1   ] dp[\ i\ ][\ 1\ ][\ 1\ ] dp[ i ][ 1 ][ 1 ] 的状态转移方程我们也能推出来了,与 d p [   i   ] [   1   ] [   0   ] dp[\ i\ ][\ 1\ ][\ 0\ ] dp[ i ][ 1 ][ 0 ] 的状态转移方程一模一样。

  1. 当第 i i i 个格子为 ? 时。

当第 i i i 个格子为 ? 时,我们需要处理 d p [   i   ] [   0   ] [   0   ] dp[\ i\ ][\ 0\ ][\ 0\ ] dp[ i ][ 0 ][ 0 ] d p [   i   ] [   0   ] [   1   ] dp[\ i\ ][\ 0\ ][\ 1\ ] dp[ i ][ 0 ][ 1 ] d p [   i   ] [   1   ] [   0   ] dp[\ i\ ][\ 1\ ][\ 0\ ] dp[ i ][ 1 ][ 0 ] d p [   i   ] [   1   ] [   1   ] dp[\ i\ ][\ 1\ ][\ 1\ ] dp[ i ][ 1 ][ 1 ]的值。

因为当第 i i i 个格子为 ? 时,第 i i i 个格子,第 i + 1 i+1 i+1 个格子和第 i − 1 i-1 i1 个格子的情况我们都不知道,所以要分情况。

考虑关于 d p [   i   ] [   1   ] [   0   ] dp[\ i\ ][\ 1\ ][\ 0\ ] dp[ i ][ 1 ][ 0 ] 的状态转移方程,在该情况下,因为第 i − 1 i-1 i1 个格子的情况我们不知道,所以将 d p [   i − 1   ] [   0   ] [   1   ] + d p [   i − 1   ] [   1   ] [   1   ] dp[\ i-1\ ][\ 0\ ][\ 1\ ]+dp[\ i-1\ ][\ 1\ ][\ 1\ ] dp[ i1 ][ 0 ][ 1 ]+dp[ i1 ][ 1 ][ 1 ] 取模后的值赋给 d p [   i   ] [   1   ] [   0   ] dp[\ i\ ][\ 1\ ][\ 0\ ] dp[ i ][ 1 ][ 0 ] 。同理, d p [   i   ] [   1   ] [   1   ] dp[\ i\ ][\ 1\ ][\ 1\ ] dp[ i ][ 1 ][ 1 ] 的状态转移方程应该与 d p [   i   ] [   1   ] [   0   ] dp[\ i\ ][\ 1\ ][\ 0\ ] dp[ i ][ 1 ][ 0 ] 的状态转移方程一模一样。

考虑关于 d p [   i   ] [   0   ] [   1   ] dp[\ i\ ][\ 0\ ][\ 1\ ] dp[ i ][ 0 ][ 1 ] 的状态转移方程,在该情况下,因为第 i − 1 i-1 i1 个格子的情况我们不知道,所以将 d p [   i − 1   ] [   0   ] [   0   ] + d p [   i − 1   ] [   1   ] [   0   ] dp[\ i-1\ ][\ 0\ ][\ 0\ ]+dp[\ i-1\ ][\ 1\ ][\ 0\ ] dp[ i1 ][ 0 ][ 0 ]+dp[ i1 ][ 1 ][ 0 ] 取模后的值赋给 d p [   i   ] [   1   ] [   0   ] dp[\ i\ ][\ 1\ ][\ 0\ ] dp[ i ][ 1 ][ 0 ] 。同理, d p [   i   ] [   0   ] [   0   ] dp[\ i\ ][\ 0\ ][\ 0\ ] dp[ i ][ 0 ][ 0 ] 的状态转移方程应该与 d p [   i   ] [   0   ] [   1   ] dp[\ i\ ][\ 0\ ][\ 1\ ] dp[ i ][ 0 ][ 1 ] 的状态转移方程一模一样。

状态转移方程我们就推完了,考虑初始化。

  1. 当第 1 1 1 个格子为 0 时。

因为第 1 1 1 个格子为 0 ,所以第 1 1 1 个格子和第 2 2 2 个格子必然没有烈焰,只需将 d p [   1   ] [   0   ] [   0   ] dp[\ 1\ ][\ 0\ ][\ 0\ ] dp[ 1 ][ 0 ][ 0 ] 赋成 1 1 1 即可。

  1. 当第 1 1 1 个格子为 1 时。

因为第 1 1 1 个格子为 1 ,所以第 1 1 1 个格子必然没有烈焰,第 2 2 2 个格子必然有烈焰(这个应该不用解释吧?),只需将 d p [   1   ] [   0   ] [   0   ] dp[\ 1\ ][\ 0\ ][\ 0\ ] dp[ 1 ][ 0 ][ 0 ] 赋成 1 1 1 即可。

  1. 当第 1 1 1 个格子为 2 时。

这种情况是无解的,不用管。

  1. 当第 1 1 1 个格子为 * 时。

因为第 1 1 1 个格子为 * ,所以第 1 1 1 个格子有烈焰,第 2 2 2 个格子的情况我们不知道,所以,需要将 d p [   1   ] [   1   ] [   0   ] dp[\ 1\ ][\ 1\ ][\ 0\ ] dp[ 1 ][ 1 ][ 0 ] d p [   1   ] [   1   ] [   1   ] dp[\ 1\ ][\ 1\ ][\ 1\ ] dp[ 1 ][ 1 ][ 1 ] 赋成 1 1 1

  1. 当第 1 1 1 个格子为 ? 时。

因为第 1 1 1 个格子为 ? ,所以第 1 1 1 个格子和第 2 2 2 个格子的情况我们都不知道,所以,需要将 d p [   1   ] [   1   ] [   0   ] dp[\ 1\ ][\ 1\ ][\ 0\ ] dp[ 1 ][ 1 ][ 0 ] d p [   1   ] [   1   ] [   1   ] dp[\ 1\ ][\ 1\ ][\ 1\ ] dp[ 1 ][ 1 ][ 1 ] d p [   1   ] [   0   ] [   1   ] dp[\ 1\ ][\ 0\ ][\ 1\ ] dp[ 1 ][ 0 ][ 1 ] d p [   1   ] [   0   ] [   0   ] dp[\ 1\ ][\ 0\ ][\ 0\ ] dp[ 1 ][ 0 ][ 0 ] 赋成 1 1 1

那么这题就完成了。

你可能感兴趣的:(题解,动态规划,深度优先,算法)