【LYZS2023暑假训练】 第二周题单总结与分析

        包括模拟、暴力、枚举、贪心、递推与递归、二分、搜索。

        值得注意的是,一道题的解法往往不止一种。 

目录

一、搜索

P1219 [USACO1.5] 八皇后 Checker Challenge

P2670 [NOIP2015 普及组] 扫雷游戏

二、贪心

P1478 陶陶摘苹果(升级版)

P1094 [NOIP2007 普及组] 纪念品分组

P2678 [NOIP2015 提高组] 跳石头 

P1080 [NOIP2012 提高组] 国王游戏

三、动态规划;递推/递归

P1002 [NOIP2002 普及组] 过河卒

P1044 [NOIP2003 普及组] 栈

P1028 [NOIP2001 普及组] 数的计算

P1164 小A点菜

四、模拟+枚举

P1618 三连击(升级版)

P1149 [NOIP2008 提高组] 火柴棒等式

五、排序

P1177 【模板】排序

P1093 [NOIP2007 普及组] 奖学金

P1116 车厢重组

P1923 【深基9.例4】求第 k 小的数


一、搜索

P1219 [USACO1.5] 八皇后 Checker Challenge

P1219 [USACO1.5] 八皇后 Checker Challenge https://www.luogu.com.cn/problem/P1219

经典模板题n皇后

题目数据为n皇后的部分情况(即6≤n≤13)

题目重点:nxn(棋盘的宽高一样,限值了变量的表达(如行号列号和对角线))

//下列代码适用于n的多种取值 
//输出函数,用于在大于3个解时输出前3个解,小于等于3个解时输出所有解 
/*
输出格式:前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。 
*/
inline int print(){
	ans++;
	if(ans<=3)//小于3个解则直接罗列,否则罗列前3个解并输出解总数 
		for(int i=1;i<=n;i++){
			if(i==n)//各解的情形输出后换行 
				write(f[i]),printf("\n");
			else	printf("%d ",f[i]);
		}
}
//用dfs方法求解 ,参数j用于枚举,表示当前的行数。运行时从1开始,向下寻找解题答案 
inline void pd(int j){
			if(j>n){
				print();
				return;
				}
	else {
	for(int a=1;a<=n;a++){//a表示当前方案中棋子的标号(n是多少棋盘就放几枚棋子)

		if(!col[a]&&!k[j-a+n]&&!h[a+j]){
		//从第一行/列开始枚举 。如果这一列的这个位置没有棋子占领,所在的两个对角线也没有棋子 
		//其中两个对角线对应的下标可证是j-a+n和a+j 
			f[j]=a,//记录方案 
			col[a]=1,//标记放置 
			h[a+j]=1,k[j-a+n]=1;
			pd(j+1);//继续搜索下一枚棋子。
			 //注意,调用下一行的情况是下一个棋子的摆放方案不存在,那么清除之前的赋值,回到上一步继续搜索 
			col[a]=0,k[j-a+n]=0,h[a+j]=0;
		}

		}
	}
}
//pd中间有一个调用自身的过程,实现了在一个方案中一行行往下找的过程 
int main(){
	read(n);
	pd(1);
	write(ans);
	return 0;
}

P2670 [NOIP2015 普及组] 扫雷游戏

https://www.luogu.com.cn/problem/P2670icon-default.png?t=N6B9http://P2670 [NOIP2015 普及组] 扫雷游戏

搜索+模拟,不难。

int n,m;
char c[105][105],ans[105][105];
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>c[i][j];
			ans[i][j]=48;
			//ASCII码中48对应‘0’,49对应‘1’,以此类推。
			//我们用字符数组记录每个格子周围的地雷数量。如果这个格子是地雷,直接用*标记为地雷。 
			//在输入初始雷区的同时初始化按下标记录地雷的数组 
			}
		}
        for(int i=1;i<=n;i++) {//开始对雷区逐个排查 
        for(int j=1;j<=m;j++) {
				if(c[i][j]=='*')//这个格子是地雷吗?是就标记 
					ans[i][j]='*';
				if(c[i][j]=='?'){//这个格子不是地雷,就排查它四面八方的地雷数量 
					  if(c[i-1][j-1]=='*')
						ans[i][j]++;
					  if(c[i-1][j]=='*')
				  		ans[i][j]++;
					  if(c[i-1][j+1]=='*')
					  	ans[i][j]++;
					  if(c[i][j-1]=='*')
					  	ans[i][j]++;
					  if(c[i][j+1]=='*')
					    ans[i][j]++;
					  if(c[i+1][j-1]=='*')
					  	ans[i][j]++;
					  if(c[i+1][j]=='*')
						ans[i][j]++;
					  if(c[i+1][j+1]=='*')
				  		ans[i][j]++;
						  }
					}
		}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++)
			cout<<ans[i][j];
			cout<<endl;
		}
 return 0;
}

二、贪心

P1478 陶陶摘苹果(升级版)

P1478 陶陶摘苹果(升级版) https://www.luogu.com.cn/problem/P1478

题意:只有摘苹果才消耗力气。所以,在高度允许的范围内,消耗力气摘尽可能多的苹果。

int a,b,n,s,num;
/*
每个苹果都有的属性:到达地上的高度和陶陶摘它需要的力气 
因此,我们给苹果写一个结构体 
*/
struct taotao{
	int x;
	int y;
} apple[MAXN];
bool cmp(taotao p,taotao q){//手写对结构体的排序规则 ,不可忽略 
	if(p.y<q.y)//先按苹果要求的力气排序,花费力气少的放前面 
		return 1;
	else return 0;
	if(p.y==q.y){//花费力气相同的情况下,高度低的放前面,方便摘苹果时按高度比较
		if(p.x<=q.x)
			return 1;
		else return 0;
	}
}
int main(){
	cin>>n>>s;
	cin>>a>>b;
	for(int i=1;i<=n;i++)
		cin>>apple[i].x>>apple[i].y;
	sort(apple+1,apple+1+n,cmp);//排序 
	for(int i=1;i<=n;i++){
		//if内的两个条件:能够到苹果,力气也够用 
		if((a+b)>=apple[i].x&&s>=apple[i].y){
			s-=apple[i].y;//摘到苹果后消耗的力气要减去 
			num++;//摘到的苹果数量+1 
		}
	}
	cout<<num<<endl;
return 0;
}

P1094 [NOIP2007 普及组] 纪念品分组

P1094 [NOIP2007 普及组] 纪念品分组 https://www.luogu.com.cn/problem/P1094

贪心+排序

每组为1-2个纪念品

int j=1,pri[30006],w,n,ans;
int main(){
	cin>>w>>n;
	for(int i=1;i<=n;i++)
		cin>>pri[i];
	sort(pri+1,pri+1+n);//先对纪念品升序排序方便分组 
	//升序排序后开头结尾向 
	while(n>=j){//此时,n表示未分组的纪念品数组末尾元素下标,j是未分组纪念品开头元素下标 
		if(pri[n]+pri[j]<=w){//开头结尾,注意分组的前提是价格之和不超过上限 
			n--;
			j++;
			ans++;	
	}
		else {//超过上限的话就把靠后的元素单独一组,从后缩小未分组纪念品的数量 
			n--;
			ans++;
		}
		}
	cout<<ans<<endl; 
return 0;
}

P2678 [NOIP2015 提高组] 跳石头 

P2678 [NOIP2015 提高组] 跳石头

重点:贪心、二分

题目对算法的限制主要在于“最大值最小”,拿走石头的数量有限,起点终点的石头不能拿走

using namespace std;
//L,N,M分别表示起点到终点的距离,起点和终点之间的岩石数,组委会至多移走的岩石数。
//nowrock记录当前的岩石序号,cnt表示记录已经移除的数量 
int i,ii,l,n,m,nowrock,rock[100005],cnt;
//二分的左右边界和中间的值 
int le,ri,mid;
int main(){
    scanf("%d%d%d",&l,&n,&m);
    for(int i=0;i<n;i++)
		scanf("%d",&rock[i]);
    le=1,ri=l;//设置二分查找的两个边界 
    //le=0也可 
//输入的数据已经是升序排序的,不用再排序处理
    while(le!=ri){//边界还能继续缩小,直到区间内只剩一个石头时停止处理 
        mid=(le+ri+1)/2;
        //mid=(le+ri)/2;会TLE一部分 
        nowrock=cnt=0;
        for(ii=0;ii<n;ii++){
            if(l-rock[ii]<mid)break;//再跳时已超过终点,则不可拿走它和它后面的石头 
            if(rock[ii]-nowrock<mid)cnt++;//跳过石头 
            else nowrock=rock[ii];//贪心
        }
        /*
		下面三行是重点 
		*/
        cnt+=n-ii;//统计已经移除的数量
        if(cnt<=m)le=mid;//如果移除的数量不够或者正好,就接着进行上述过程,改变区间左边界 
        else ri=mid-1;//否则改变区间右边界 
    }
    printf("%d",le);
    return 0;
}

P1080 [NOIP2012 提高组] 国王游戏

这个没做 累了

P1080 [NOIP2012 提高组] 国王游戏 https://www.luogu.com.cn/problem/P1080

知识点:贪心、高精度

贴一个解题链接

贪心EX

三、动态规划;递推/递归

P1002 [NOIP2002 普及组] 过河卒

P1002 [NOIP2002 普及组] 过河卒 https://www.luogu.com.cn/problem/P1002

状态转移方程    dp[i][j]=dp[i-1][j]+dp[i][j-1];

因为棋子可以向右或者向下,它到达一个点的前提是上一步在它的上面或左面

//x,y表示马的位置,m,n表示B点位置; a记录该点能否走,dp记录到某点的路线数量 
long long x,y,m,n,a[25][25],dp[25][25];
void set(int x,int y) {//按马的行走规则进行搜索,标记棋子可以通过的点 
	a[x][y]=1;//马本身所在的位置是马到达了的,用1标记 
	//开始按马的行走方式,对它可以到达并拦截棋子的位置标记 
	//各个if中一定都是大于等于而非等于,因为各个点的下标都是非负数,要判断可到达的位置的下标是合法的 
	if(x-2>=0&&y-1>=0)
		a[x-2][y-1]=1;
	if(x+2>=0&&y-1>=0)
		a[x+2][y-1]=1;
	if(x-2>=0&&y+1>=0)
		a[x-2][y+1]=1;
	if(x+2>=0&&y+1>=0)
		a[x+2][y+1]=1;
	if(x-1>=0&&y-2>=0)
		a[x-1][y-2]=1;
	if(x-1>=0&&y+2>=0)
		a[x-1][y+2]=1;
	if(x+1>=0&&y-2>=0)
		a[x+1][y-2]=1;
	if(x+1>=0&&y+2>=0)
		a[x+1][y+2]=1;
}
int main() {
		//输入马的位置和目的地B点的位置 
		//注意对于棋盘,各行各列的下标都是从0开始 
	cin>>x>>y>>n>>m; 
		set(n,m);//标记B点周围棋子不被马拦截的点
		dp[1][0]=1;//设置递推边界
		//两个for的变量都从1开始才能保证自始至终i-1和j-1表达的下标是合法的(行列都从0开始标记,要控制它们是非负数) 
		for(int i=1; i<=x+1; i++)
			for(int j=1; j<=y+1; j++){
				//所以从循环开始,i表示的是i-1的位置,j表示的是j-1的位置
				//相当于我们求解时把下标全都手动-1了 
				dp[i][j]=dp[i-1][j]+dp[i][j-1];//状态转移方程 
				if(a[i-1][j-1]==1)//如果这个点是马能达到的,那么这个点不能走,路径数归零 
					dp[i][j]=0;
			}
			//输出时要把前面所说的变动改回去 
			//dp[x+1][y+1]表达的是到dp[i][j]有多少条路 
		cout<<dp[x+1][y+1]<<endl;
	return 0;
}

P1044 [NOIP2003 普及组] 栈

P1044 [NOIP2003 普及组] 栈 https://www.luogu.com.cn/problem/P1044

递归

#define maxn 1005
int f[maxn][maxn],n;
//递归 
//f[x][y],下标 x表示队列里待排的数,y表示栈里的数,f[x][y]表示此时的情况的结果 
inline int js(int x,int y){
	if(f[x][y])//已经有结果的不用再重复求 
		return f[x][y];
		
	if(x==0)//递归边界,也就是数字全部进栈了,只有1种情况 
		return 1;
		
	if(y>0) //队列里有数字,栈有两种可能 
	/*栈空时,只能进入,所以队列里的数-1,栈里的数+1,即加上 f[x-1][y+1];
	栈不空时,那么此时有出栈1个或者进1再出1个共两种种情况,分别加上 f[x-1][y+1] 和 f[x][y-1] 
	*/
	/*下面两行顺序不能调换,否则结果有误*/
		f[x][y]+=js(x,y-1); //这句可以直接写,是因为栈不空它才发生,栈空时y=0,y-1<0不合法,不会计算 

	f[x][y]+=js(x-1,y+1);//栈要么空要么不空,所以这个式子在两种情况都有,但是写一次就行
	return f[x][y];
}
int main(){
	cin>>n;
	cout<<js(n,0);
	return 0;
}

P1028 [NOIP2001 普及组] 数的计算

P1028 [NOIP2001 普及组] 数的计算 https://www.luogu.com.cn/problem/P1028

#define maxn 1005
int f[maxn],n;
inline int dg(int n){
	if(n==1||n==0)//只有一个数字 n的数列是一个合法的数列
	//n=1时的数列都只有一个包括它们本身的数列 
	//n=0是递归边界 
		return 1;
	if(f[n])//已经有结果的不用再求 
		return f[n];
	int ans=0;//统计结果的中间变量。
	//两个合法数列 a,b不同当且仅当两数列长度不同或存在一个正整数 i≤∣a∣∣,使得 ai≠bi。
		for(int j=1;j<=n/2;j++)//从短到长求数列 ,数列尾部新加的元素符合要求2 
			ans+=dg(j);
	return  f[n]=ans+1;//+1是因为尾部每次加一个数都生成一个新数列,每次上一行调用dg()时,j都会调用第一个或这一个return 
}
int main(){
	cin>>n;
	dg(n);
	cout<<dg(n);
	return 0;
}

P1164 小A点菜

P1164 小A点菜 https://www.luogu.com.cn/problem/P1164

类似01背包的动态规划

//dp[i][j],i表示看过的菜,j表示状态下花费的钱 
int dp[maxn][maxn],v[maxn],n,m; 
int main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++)
		v[i]=read(); 
		//开始支配钱包。一道道菜开始看 
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++){
			//每种菜只有一份+所有钱花光-->菜品从头到尾枚举,相当于01背包 
			if(j<v[i])//此时买不起这道菜 
				dp[i][j]=dp[i-1][j];//看过这道菜和看过这道菜之前的点餐状态是一样的 
			if(j==v[i])//刚好买得起?买!在看过上一道菜的基础上点餐数+1 
				dp[i][j]=dp[i-1][j]+1;
			if(j>v[i])//买得起,买完还有剩余的钱 
			//下一行中当前状态下, 达到看过i道菜花费j元的状态。这个状态分两部分:看过i-1道菜花j元的状态一样可以达到(因为有余额),看过 i-1道菜花j-v[i]元的状态也能达到 
				dp[i][j]=dp[i-1][j]+dp[i-1][j-v[i]];
		}
	write(dp[n][m]);
	return 0;
}

四、模拟+枚举

模拟:按题意的意思直接直接直接写代码。

P1618 三连击(升级版)

P1618 三连击(升级版) https://www.luogu.com.cn/problem/P1618

模拟+枚举

using namespace std;
//记录三个三位数各个位的变量;统计数字使用情况的数组;比例变量;标记变量 
int i,j,l,n,m,x,y,z,d,e,f,g,h,k,num[9],a,b,c,flag,v;
int main() {
	cin>>a>>b>>c;
	/*下一行的判断针对新增的某测试数据。
	题目描述的是数字1-9,但输入格式中并未对三个数限制,存在有0的情况
	此时不满足题目要求,输出No!!!即可 */
		if(a==0||b==0||c==0){
	cout<<"No!!!"<<endl;
	return 0;
		}
	//我们不知道输出结果可能有多少个,所以判断一组输出一组就可以了,也比较节省空间 
	//输出要求升序排列,那么从小到大判断就行
	//输出的都是三位数,那么最大的数不超过999,最小的数不超过333 
	for(i=1; i<=3; i++)//开始枚举第一个数的百位、十位、个位 
		for(j=1; j<=9; j++)
			for(l=1; l<=9; l++) {
				x=100*i+10*j+l;//第一个数x 
				y=x*b/a;//第二个数 
				z=x*c/a;//第三个数 
				d=y/100;//第二个数的百十个位 
				e=(y%100)/10;
				f=y%10;
				g=z/100;//第三个数的百十个位 
				h=(z%100)/10;
				k=z%10;
				//把三个数的各个位数字,共9个,存入数组方便判断是否是1-9 
				num[0]=i;
				num[1]=j;
				num[2]=l;
				num[3]=d;
				num[4]=e;
				num[5]=f;
				num[6]=g;
				num[7]=h;
				num[8]=k;
				//开始判断,从头到尾对数组元素两两比较。 相邻数字相同,或数字中有0,就不符合要求,赋值给标记变量 
				for(m=0; m<=8; m++)
					for(n=m+1; n<=8; n++) {
						if(num[m]==num[n]||num[m]==0||num[n]==0)
							v=1;//标记变量之一,用于判断对组成的数字的约束,即9个数为1-9 
					}
				if(v!=1&&z<=999){//最终输出的要求是9个数都用到了,而且最大的数是3位数 
					flag++;//统计生成的组数 
					cout<<x<<" "<<y<<" "<<z<<endl;//输出 
					}
					v=0;//v归零 
			}
	if(!flag)//如果一组也没生成,就输出无解 
	cout<<"No!!!"<<endl;
	return 0;
}

P1149 [NOIP2008 提高组] 火柴棒等式

P1149 [NOIP2008 提高组] 火柴棒等式 https://www.luogu.com.cn/problem/P1149

模拟+枚举+递归

(有点枚举但不多)

using namespace std;
//match数组记录每个数字所需的火柴数,下标表示数字,数组元素表示对应下标数字需要的火柴 
int match[10]={6,2,5,5,4,5,6,3,7,6},n,ans;
inline int solve(int x){//求摆一个数字需要多少根 
	int s=0;//用s记录摆x需要的火柴数量 
	if(x==0)//数字0可以直接返回match[0]的值 
	return match[0];
	while(x>0){//x>0时,不管它有多少位,先用%求最后一位,s记录摆最后一位的火柴数,再用/把最后一位去掉 
		s+=match[x%10];
		x=x/10;
	}
	return s;
}
//使用火柴最少的数字是1(2根) ;加号和等号一共需要4根 
int main(){
	cin>>n;
	for(int i=0;i<=1111;i++)
		for(int j=0;j<=1111;j++){
			if(solve(i)+solve(j)+solve(i+j)+4==n)
				++ans;
		}
		cout<<ans;
	return 0;
}

五、排序

P1177 【模板】排序

P1177【模板】排序 https://www.luogu.com.cn/problem/P1177

不放代码了 排序有很多种 各有千秋

P1093 [NOIP2007 普及组] 奖学金

P1093 [NOIP2007 普及组] 奖学金 https://www.luogu.com.cn/problem/P1093

结构体的定义及应用+结构体排序规则

int n;
struct student{
	int num;
	int sum;
	int chi;
	int mat;
	int eng;
}pui[305];
bool cmp(student a,student b){
	if(a.sum>b.sum)
		return 1;
	else if(a.sum<b.sum)
		return 0;
	if(a.chi>b.chi)
		return 1;
	else if(a.chi<b.chi)
		return 0;
	if(a.num<b.num)
		return 1;
	else if(a.num>b.num)
		return 0;
		}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		pui[i].num=i;
	cin>>pui[i].chi>>pui[i].mat>>pui[i].eng;
	}
	for(int i=1;i<=n;i++)
		pui[i].sum=pui[i].chi+pui[i].mat+pui[i].eng;
	sort(pui+1,pui+1+n,cmp);
	for(int i=1;i<=5;i++){
		cout<<pui[i].num<<' '<<pui[i].sum<<endl;
	}
	return 0;
}

P1116 车厢重组

P1116 车厢重组 https://www.luogu.com.cn/problem/P1116

模拟冒泡排序,并计算交换了多少次

也可以用其它排序方法实现

#define MAXN 100005
int n[MAXN],num,ans;
int main(){
	cin>>num;
	for(int i=1;i<=num;i++)
		cin>>n[i];
	for(int i=1;i<num;i++)
	for(int j=i;j<=num;j++){
		if(n[i]>n[j])
			ans++;
	}
	cout<<ans;
	return 0;
}

P1923 【深基9.例4】求第 k 小的数

P1923 【深基9.例4】求第 k 小的数 https://www.luogu.com.cn/problem/P1923

模板,二分法求第k小的数

#define MAXN 5000005
int x[MAXN],k,n;
void qsort(int l,int r){
	int i=l,j=r,mid=x[(l+r)/2];
	do{
		while(x[j]>mid)	j--;
		while(x[i]<mid) i++;
		if(i<=j){
			swap(x[i],x[j]);
			i++;
			j--;
		}
	}while(i<=j);
	if(k<=j)	qsort(l,j);
	else if(i<=k)	qsort(i,r);
	else{
		printf("%d",x[j+1]);
	}
	return;
} 
int main(){
	scanf("%d%d",&n,&k);
	for(int i=0;i<=n;i++)
		scanf("%d",&x[i]);
	qsort(0,n-1);
	return 0;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

延7488

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值