9.13 a.m.小结

T1:问题 A: 营救

题目描述

铁塔尼号遇险了!他发出了求救信号。距离最近的哥伦比亚号收到了讯息,时间就是生命,必须尽快赶到那里。
通过侦测,哥伦比亚号获取了一张海洋图。这张图将海洋部分分化成 n*n 个比较小的单位,其中用 1 标明的是陆地,用 0 标明是海洋。船只能从一个格子,移到相邻的四个格子。
为了尽快赶到出事地点,哥伦比亚号最少需要走多远的距离。

输入

第一行为 n,下面是一个 n*n 的 0、1 矩阵,表示海洋地图。
最后一行为四个小于 n 的整数,分别表示哥伦比亚号和铁塔尼号的位置。

输出

哥伦比亚号到铁塔尼号的最短距离,答案精确到整数。

样例输入

3

001

101

100

1 1 3 3

样例输出

4

提示


【数据范围】

N<=1000

题解

这道题考的是宽搜(深搜无理由退货tle),由于宽搜的性质,当第一次找到终点的时候,此刻的距离就是最短距离。为了描述方便,队列中存结构体,包含x、y,这样就能准确描述出点坐标(哈希当然也行),然后预处理出4个方向,每次取出队首,然后向四周扫描,发现可以入队的(当且仅当不超界,不是陆地,没走到过)就入队。

参考代码

#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>

using namespace std;
struct node
{
	int x,y,depth;
};
queue<node>q;
int dx[5]={0,0,1,0,-1};
int dy[5]={0,1,0,-1,0};
int n,e_map[2001][2001],st_x,st_y,ed_x,ed_y;
char s[2001];
int vis[2001][2001],ans=9999999;
bool pd(int x,int y) 
{ return (x>0)&&(x<=n)&&(y>0)&&(y<=n); }
void dfs(int x,int y,int depth)
{
	//printf("x=%d y=%d\n",x,y);
	vis[x][y]=depth;
	for(int i=1;i<=4;i++)
	{
		int x1=x+dx[i],y1=y+dy[i];
		if(!pd(x1,y1)) continue;
		if(depth+1>=vis[x1][y1]) continue;
		if(e_map[x1][y1]==1) continue; 
		dfs(x1,y1,depth+1);
	}
}
int main()
{
	//freopen("1.in","r",stdin);
	memset(vis,-1,sizeof(vis));

	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s);
		for(int j=1;j<=n;j++)
		{
			e_map[i][j]=s[j-1]-'0';
		}
	}
	scanf("%d%d%d%d",&st_x,&st_y,&ed_x,&ed_y);
	//dfs(st_x,st_y,0);
	node hr;
	hr.x=st_x;
	hr.y=st_y;
	hr.depth=0;
	q.push(hr);
	vis[st_x][st_y]=0;
	while(!q.empty())
	{
		node pt=q.front();q.pop();
		int x=pt.x,y=pt.y,depth=pt.depth;
		//printf("x=%d y=%d\n",x,y);
		if(x==ed_x&&y==ed_y) 
		{
			  ans=min(ans,vis[x][y]);
		}
		for(int i=1;i<=4;i++)
		{
			int x1=x+dx[i],y1=y+dy[i];
			if(!pd(x1,y1)) continue;
			if(vis[x1][y1]!=-1) continue;
			if(e_map[x1][y1]==1) continue; 
			node ht;
			ht.x=x1;
			ht.y=y1;
			ht.depth=depth+1;
			vis[x1][y1]=depth+1;
			q.push(ht);
		}	
	}
	printf("%d",ans);
	return 0;
}

T2:问题 B: 细胞

题目描述

一矩形阵列由数字0到9组成,数字1到9代表细胞,细胞的定义为沿细胞数字上下左右还是细胞数字则为同一细胞,求给定矩形阵列的细胞个数。

输入

第一行两个整数,之间用一个空格隔开,表示矩阵的行数m和列数n。

第二行到第m+1行为mxn数字矩阵,数字之间没有空格。

输出

一行一个整数,表示细胞个数。

样例输入

4 10

0234500067

1034560500

2045600671

0000000089

样例输出

4

提示

【算法分析】

    (1)从文件中读人m* n矩阵阵列,将其转换为boolean矩阵存人bz数组中;

    (2)沿bz数组矩阵从上到下、从左到右,找到遇到的第一个细胞;

    (3)将细胞的位置人队h,并沿其上、下、左、右四个方向上的细胞位置入队,入队后的位置bz数组置为false;

    (4)将h队的队头出队,沿其上、下、左、右四个方向上的细胞位置入队,入队后的位置bz数组置为false;

    (5)重复(4),直至h队空为止,则此时找出了一个细胞;

    (6)重复(2),直至矩阵找不到细胞;

    (7)输出找到的细胞数。

题解

通过题中描述,细胞是指有数字的连通块的个数,因此考虑宽搜。首先全地图找到一个有颜色的,然后将这个点入队,访问四周是否有相同颜色,若有,也入队,并且标记。每当找到这样一组连通块,答案就累加1,从(1,1)到(n,n)扫描一遍即可。

参考代码

#include<cstdio>
#include<queue>
using namespace std;
struct node
{
	int x,y;
};
queue<node>q;
char s[2001];int ans=0;
int dx[5]={0,0,1,0,-1};
int dy[5]={0,1,0,-1,0};
int n,m,e_map[1001][1001],vis[1001][1001];
bool pd(int x,int y)
{ return (x>0)&&(x<=n)&&(y>0)&&(y<=m); }
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s);
		for(int j=1;j<=m;j++)
		{
			e_map[i][j]=s[j-1]-'0';
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(!vis[i][j]&&e_map[i][j]>0)
			{
				ans++;
				node pt1;
				pt1.x=i;pt1.y=j;
				q.push(pt1);
				for(int k=1;k<=4;k++)
				{
					int x1=i+dx[i],y1=j+dy[i];
					if(pd(x1,y1)&&e_map[x1][y1]>0)
					{
						node hr;
						hr.x=x1;hr.y=y1;
						q.push(hr);
					}
				}
				while(!q.empty())
				{
					node pt=q.front();q.pop();
					int x=pt.x,y=pt.y;
					vis[x][y]=1;
					for(int i=1;i<=4;i++)
					{
						int x1=x+dx[i],y1=y+dy[i];
						if(pd(x1,y1)&&!vis[x1][y1]&&e_map[x1][y1]>0)
						{
							node puth;
							puth.x=x1;puth.y=y1;
							q.push(puth);
						}
					}
				}
			}
		}
	}
	printf("%d",ans);
	return 0;
}

T3:问题 C: 体积

题目描述

    给出n件物品,每件物品有一个体积Vi,求从中取出若干件物品能够组成的不同的体积和有多少种可能。例如,n=3,V=(1,3,4),那么输出6,6种不同体积和具体为1、3、4、5、7、8。

输入

    第1行1个正整数,表示n。
    第2行n个正整数,表示Vi,每两个数之间用一个空格隔开。

输出

    一行一个数,表示不同的体积和有多少种可能。

样例输入

1 3 4

样例输出

6

提示


【数据规模】

    对于30%的数据满足:n≤5,Vi≤10。

    对于60%的数据满足:n≤10,Vi≤20。

    对于100%的数据满足:n≤20,1≤Vi≤50。

题解

这道题数据强度较弱,因此直接深搜。首先来一个pd函数,表示枚举每一种可能的选与不选,效率是指数级,但也能在效率之内,然后用一个桶来存(如果数据大了就可以用邻接表来存),最后每次找到新的答案就ans++,或者最后最后枚举一共有多少个解。

参考代码

#include<cstdio>
using namespace std;
int n,a[30],sum=0,vis[30],pt[1001],ans;
int max1=-99999;
void dfs(int k)
{
	if(k==n+1)
	{
		if(!pt[sum]&&sum>0) ans++;
		pt[sum]=1;
		return;
	}
	sum+=a[k];
	dfs(k+1);
	sum-=a[k];
	dfs(k+1);
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	dfs(1);
	printf("%d\n",ans);
	return 0;
}

T4:问题 D: 数的拆分

题目描述

    输人一个整数n,输出n拆分成若干正整数和的所有方案,即n=S1+S2+..+Sk.的形式,且S1≤S2≤...Sk,n≤20,请按照字典序输出。

输入

    一行一个整数n。

输出

    所有拆分方案,具体格式参见输出样例。

样例输入

4

样例输出

1+1+1+1

1+1+2

1+3

2+2

4

total=5

题解

由于要输出所有情况,因此不会是dp或其他算法,直接搜就行了。因为要求按照字典序输出,所以搜索的顺序要特殊注意:后面枚举的要比前面的大或者相等,本身也可以作为答案,但是从n/2+1到n-1这个区间(如果合法)都是不存在答案的,因此又可以做一些优化。每得到一个答案,直接输出并且答案累加1。最后输出total=k即可。

参考代码

#include<cstdio>
using namespace std;
int n,tot=0,q[30];
void dfs(int k,int sum,int dep)
{
	if(sum==n) 
	{
		tot++;
		if(k==n) 
		{
			printf("%d\n",k);
			return;
		}
		for(int i=1;i<dep;i++)
		printf("%d+",q[i]);
		printf("%d\n",k);
		return;
	}
	q[dep]=k;
	for(int i=k;i+sum<=n;i++)
	dfs(i,sum+i,dep+1);
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	dfs(i,i,1);
	printf("total=%d",tot);
	return 0;
}

T5:问题 E: 全排列问题

题目描述

输出自然数 1 到 n 所有不重复的排列,即 n 的全排列,要求所产生的任一数字序列中不允许出现重复的数字。

输入

一行一个整数n(1≤n≤9)。

输出

由1~n 组成的所有不重复的数字序列,每行一个序列,一个数占5格。

样例输入

3

样例输出

    1    2    3

    1    3    2

    2    1    3

    2    3    1

    3    1    2

    3    2    1

题解

这道题没啥多说的,直接就是搜索。此刻需要一个vis数组表示是否选了这个数,然后通过指数的效率就能过。注意输出是printf(“%5d”,k)表示一个数占5位。

参考代码

#include<cstdio>
using namespace std;
int vis[20],n,s[20],sum;
void dfs(int k)
{
	if(k==n+1) 
	{
		for(int i=1;i<=n;++i) printf("    %d",s[i]);
		printf("\n");
		return;
	}
	if(k==n)
	{
		vis[sum]=1;
		s[k]=sum;
		dfs(k+1);
		vis[sum]=0;
		return ;
	}
	for(int i=1;i<=n;++i)
	{
		if(vis[i]) continue;
		vis[i]=1;s[k]=i;sum-=i;
		dfs(k+1);
		vis[i]=0;sum+=i;
	}
}
int main()
{
	scanf("%d",&n);
	sum=(1+n)*n/2;
	dfs(1);
	return 0;
}

T6:问题 F: 最佳调度问题

题目描述

假设有n个任务由k个可并行工作的机器完成。完成任务i需要的时间为ti。试设计一个算法找出完成这n个任务的最佳调度,使得完成全部任务的时间最早。
对任意给定的整数n和k,以及完成任务i需要的时间为ti,i=1~n。编程计算完成这n个任务的最佳调度。

输入

第一行有2 个正整数n和k。第2 行的n个正整数是完成n个任务需要的时间。

n<=20 k<=20 ti<=100

输出

一行一个数,表示完成全部任务的最早时间。

样例输入

7 3

2 14 4 16 6 5 3

样例输出

17

题解

这道题有人用贪心吗?看你怎么贪了,我这有一个数据卡了80%的贪心....不说了,来谈谈搜索吧,虽然和上两道题一样,都是指数级,但是这个的数据量明显大得多。因此要考虑剪枝。单纯的枚举,肯定是枚举第i个任务交给第j个机器,现在来看看有哪些可以优化。首先,从耗时大的任务开始枚举可以小部分减少枚举量,其次,如果此刻完成任务时间大于等于已有的最优时间,那直接return肯定的。通过这两个小优化,就能极大的提高效率。

参考代码

#include<cstdio>
#include<algorithm>
using namespace std;
int n,k,vis[30],a[30],z[30],maxn=-1,minn=999999999;
bool comp1(int p,int q) { return p>q; }
void dfs(int k1)
{
	if(maxn>=minn) return; 
	if(k1==n+1)
	{
		if(maxn<minn) minn=maxn;
		return;
	}
	for(int i=1;i<=k;i++)
	{
		if(z[i]+a[k1]>=minn) continue;
		int max1=maxn;
		z[i]+=a[k1];
		//printf("k1=%d z[%d]=%d\n",k1,i,z[i]);
		if(maxn<z[i]) maxn=z[i];
		dfs(k1+1);
		z[i]-=a[k1];maxn=max1;
	}
}
int main()
{
	//freopen("1.out","w",stdout);
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	sort(a+1,a+n+1,comp1);
	dfs(1);
	printf("%d",minn);
	return 0;
}
/*
7 3
2 14 4 16 6 5 3

*/

T7:问题 G: 埃及分数

题目描述

在古埃及,人们使用单位分数的和(形如1/a的, a是自然数)表示一切有理数。如:2/3=1/2+1/6,但不允许2/3=1/3+1/3,因为加数中有相同的。对于一个分数a/b,表示方法有很多种,但是哪种最好呢?首先,加数少的比加数多的好,其次,加数个数相同的,最小的分数越大越好。
如:19/45=1/3 + 1/12 + 1/180
19/45=1/3 + 1/15 + 1/45
19/45=1/3 + 1/18 + 1/30,
19/45=1/4 + 1/6 + 1/180
19/45=1/5 + 1/6 + 1/18.
最好的是最后一种,因为1/18比1/180,1/45,1/30,1/180都大。
给出a,b(0<a<b<1000),编程计算最好的表达方式。

输入

一行两个自然数,用一个空格隔开,表示a、b。

输出

若干个数,自小到大排列,依次是单位分数的分母。

样例输入

19 45

样例输出

5 6 18

题解

暂无

T8:问题 H: 金币问题II

题目描述

国王将金币作为工资,发放给忠诚的骑士。第一天,骑士收到一枚金币;之后两天(第二天和第三天),每天收到两枚金币;之后三天(第四、五、六天),每天收到三枚金币;之后四天(第七、八九、十天),每天收到四枚金币,....这种工资发放模式会-直这样延续下去。当连续N天每天收到N枚金币后,骑士会在之后的连续N+1天里,每天收到N+1枚金币。

    请编程计算在从第一天开始的给定天数内,骑士一共获得了多少金币。

输入

输入包含至少一行,单不多于1000行

除最后一行外,输入的每行是一组输入数据,包含一个正整数n,表示天数。

输入的最后一行为0,表示输入结束。

输出

对每个数据输出一行一个整数,表示该数据对应的金币总数。

样例输入

10

6

7

11

15

16

100

10000

1000

21

22

0

样例输出

30

14

18

35

55

61

945

942820

29820

91

98

提示


对于60%的数据满足:n<=10^3


对于80%的数据满足:n<=10^6


对于100%的数据满足:n<=10^12

题解

解决这道题需要用到两个公式:等差数列和平方前缀和公式。

此处只给出平方前缀:

 

首先需要二分出此刻应该领多少金币,可用等差数列求解,其次就是算出从第一天起到当前金币-1的最后一天一共领了多少金币,可用平方前缀解决。最后,用余下的天数乘以当前领的金币数,就是后一部分答案,将两部分答案相加,就能得到最终的金币数。注意要开unsigned long long。

参考代码

#include<cstdio>
using namespace std;
int main()
{
	unsigned long long ans,n,l=1ll,r=n,mid; 
	while(scanf("%llu",&n))
	{
		if(n==0) break;
		l=1ll;r=n;
		while(l<r)
		{
			mid=(l+r)/2ll;
			if(mid*(mid+1ll)/2ll<n) l=mid+1ll;
			else r=mid; 
		}
		ans=l*(l-1ll)*(2ll*l-1ll)/6ll;
		ans+=(n-l*(l-1ll)/2ll)*l;
		printf("%llu\n",ans);
	}
	return 0;
}

T8:问题 I: 生日蛋糕

题目描述

7月17日是Mr.W的生日,ACM-THU为此要制作一个体积为Nπ的M层生日蛋糕,每层都是一个圆柱体。
设从下往上数第i(1 <= i <= M)层蛋糕是半径为Ri, 高度为Hi的圆柱。当i < M时,要求Ri > Ri+1且Hi > Hi+1。
由于要在蛋糕上抹奶油,为尽可能节约经费,我们希望蛋糕外表面(最下一层的下底面除外)的面积Q最小。
令Q = Sπ
请编程对给出的N和M,找出蛋糕的制作方案(适当的Ri和Hi的值),使S最小。
(除Q外,以上所有数据皆为正整数)

输入

有两行,第一行为N(N <= 10000),表示待制作的蛋糕的体积为Nπ;第二行为M(M <= 20),表示蛋糕的层数为M。

输出

仅一行,是一个正整数S(若无解则S = 0)。

样例输入

100

2

样例输出

68

提示

【提示】

圆柱公式

体积V = πR2H

侧面积A' = 2πRH

底面积A = πR2

题解

这道题的剪枝,剪枝无法说什么了。先老老实实枚举过样例吧,从最初的状态(一层都没)开始枚举,每次记录剩下的体积rest和总表面积s,以及当前层数dep、半径r以及当前高度h,然后枚举下一层的合法的r、h并且递归下去即可。初步估测半径和高度都不太可能是较大的值,因此直接赋一个25到30 的一个数。然后每次更新最小表面积,最后输入答案即可。OK,能过样例了,现在再来看剪枝。首先,当前表面积大于最小表面积,可以直接退出;如果层数多于最多层数,也退出;剩余体积为负,退出;剩余体积小于剩余最大体积,退出;剩下表面积加上当前表面积大于等于最小表面积,退出;最后也是最关键的一点:满足当前层数的最大体积大于等于剩余体积,退出!如此就能在规定时间内跑完所有范围。

参考代码

#include<cstdio>
using namespace std;
int n,m,mins=999999999;
int Min[310],Max[310];
inline void dfs(int dep,int r,int h,int rest,int s)
{
	//printf("dep=%d r=%d h=%d rest=%d s=%d\n",dep,r,h,rest,s);
	if(dep==m)
	{
		if(s<mins&&rest==0) mins=s;
		return;
	}
	if(s>mins) return;
	if(dep>m+1) return;
	if(rest<0) return;
	if(Min[m-dep]>rest) return;
	if(Max[m-dep]+s>=mins) return;
	if(2*rest/r+s>=mins) return;
	if(rest>(r-1)*(r-1)*(h-1)*(m-dep)) return;
	for(int i=m-dep;i<r;++i)
	  for(int j=m-dep;j<h;++j)
	  	if(dep+1<=m&&rest>=i*i*j)
		  dfs(dep+1,i,j,rest-i*i*j,s+2*i*j);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=23;++i) 
	{
		Min[i]=Min[i-1]+i*i*i;
		Max[i]=Max[i-1]+2*i*i;
	}
	for(int i=m;i<=28;++i)
		for(int j=m;j<=28;++j)
		    dfs(1,i,j,n-j*i*i,i*i+2*i*j);
	if(mins==999999999) printf("0");
	else printf("%d",mins);
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值