9.2 a.m.小结

T1问题 A: 数塔问题

题目描述

如图所示为一个数字三角形。请编一个程序计算从顶到底的某处的一条路径,使该路径所经过的数字总和最大。只要求输出总和。
    1.一步可沿左斜线向下或右斜线向下走;
    2.三角形行数小于等于100;
    3.三角形中的数字为0,1, ... ,99;

 

输入

第一行一个正整数,表示数字三角形的行数,下面若干行为数字三角形,数据应以如图所示格式输入:

输出

一行一个整数,表示答案。

样例输入

5

7

3 8

8 1 0

2 7 4 4

4 5 2 6 5

样例输出

30

提示

【算法分析】

    此题解法有多种,从递推的思想出发,设想,当从顶层沿某条路径走到第i层向第i+1层前进时,我们的选择一定是沿其下两条可行路径中最大数字的方向前进,为此,我们可以采用倒推的手法,设a[i][j]存放从i,j出发到达n层的最大值,则a[i][j]=max{a[i][j]+a[i+1][j],a[i][j]+a[i+1][j+1]},a[1][1]即为所求的数字总和的最大值。

题解

对于这道题,仿佛提示更适合作为题解..

现在用递归的思路重整这道题。

从最后一排出发,要想求到,

可以由上面两个值转移过来,即。但是又由于和又未知,但可以由更上面一层得到,因此采用dfs来解决。尤其要注意边界问题,就是dp[1][1]=map[1][1]。

递推和递归本就是互逆的过程。(汉诺塔——)

参考代码

#include<cstdio>
using namespace std;
int dp[200][200],n,a[200][200],maxn=-9999999;
int max1(int p,int q) { return p>q?p:q; }
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=i;j++)
	scanf("%d",&a[i][j]);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=i;j++)
	{
		if(j==1) dp[i][j]=dp[i-1][j]+a[i][j];
		else if(j==i) dp[i][j]=dp[i-1][j-1]+a[i][j];
		else dp[i][j]=max1(dp[i-1][j],dp[i-1][j-1])+a[i][j];
	}
	for(int i=1;i<=n;i++)
	if(dp[n][i]>maxn) maxn=dp[n][i];
	printf("%d",maxn);
	return 0;
}

T2:问题 B: 回家的路

题目描述

军训结束后,同学们高兴的从学校走回自己的家,每位同学都会走最近的路线,德育处老师希望知道最远的同学回到自己的家需要多长时间,假设从家附近路口到家的时间忽略不计,你可以帮老师找出这个时间吗?
   

输入

第一行是两个整数N、M,N表示成都的街道上有几个路口,M表示在成都有几条路。接下来M行,每行包括3个整数A,B,C(1<=A,B<=N,1<=C<=30),表示在路口A与路口B之间有一条路,走过这条路需要C分钟的时间。   标号为1的路口是树德中学所在地,其它标号的路口至少有一位树德学子在此附近居住,输入数据保证每位树德学子都能平安回到自己的家。 

[数据规模]

20%的数据 ,n<=100,m<=100
70%的数据,n<=1000,m<=5000
100%的数据,n<=10000,m<=20000
  

输出

输出一个整数,表示最远的同学回到家的分钟数。

样例输入

3 3

1 2 5

2 3 5

3 1 2

样例输出

5

题解

由题可见,这就是道dj和SPFA的板题,此处给出SPFA和DJ两个代码。

SPFA参考代码

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
int d[1000001],vis[1000001];
queue<int>q;
struct tree
{
	int nxt,to,dis;
}tr[1000001];
int head[1000001],cnt=0;
void build_tree(int u,int v,int d)
{
	tr[++cnt].nxt=head[u];
	tr[cnt].to=v;
	tr[cnt].dis=d;
	head[u]=cnt;
}
int n,m;
int main()
{
	memset(d,127/3,sizeof(d));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int u,v,d;
		scanf("%d%d%d",&u,&v,&d);
		build_tree(u,v,d);
		build_tree(v,u,d);
	}
	d[1]=0;vis[1]=1;
	q.push(1);
	while(!q.empty())
	{
		int pt=q.front();q.pop();
		vis[pt]=0;
		for(int i=head[pt];i;i=tr[i].nxt)
		{
			int to=tr[i].to;
			if(d[pt]+tr[i].dis<d[to])
			{
				d[to]=d[pt]+tr[i].dis;
				if(!vis[to])
				{
					vis[to]=1;
					q.push(to);
				}
			}
		}
	}
	int max1=-1;
	for(int i=2;i<=n;i++)
	  if(d[i]>max1)
	    max1=d[i];
	printf("%d",max1);
	return 0;
}

DJ参考代码

#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
struct tree
{
	int nxt,to,dis;
}tr[1000001];
int head[1000001],cnt=0;
void build_tree(int u,int v,int d)
{
	tr[++cnt].nxt=head[u];
	tr[cnt].dis=d;
	tr[cnt].to=v;
	head[u]=cnt;
}
struct node
{
	int num,dis;
};
priority_queue<node>q;
bool operator < (node m,node n)
{
	return m.dis>n.dis;
}
int n,m,d[1000001],vis[1000001];
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int u,v,d;
		scanf("%d%d%d",&u,&v,&d);
		build_tree(u,v,d);
		build_tree(v,u,d);
	}
	node hr;d[1]=0;
	hr.dis=0;hr.num=1;
	q.push(hr);
	while(!q.empty())
	{
		node pc=q.top();q.pop();
		int pt=pc.num;
		if(vis[pt]) continue;
		vis[pt]=1;d[pt]=pc.dis;
		for(int i=head[pt];i;i=tr[i].nxt)
		{
			int to=tr[i].to;
			if(vis[to]) continue;
			node ht;
			ht.dis=d[pt]+tr[i].dis;
			ht.num=to;
			q.push(ht);
		}
	}
	int max1=-1;
	for(int i=2;i<=n;i++)
	if(d[i]>max1) max1=d[i];
	printf("%d",max1);
	return 0;
}

T3问题 C: 真二叉树重构

题目描述

一般来说,给定二叉树的先序遍历序列和后序遍历序列,并不能确定该二叉树。

 

(图一)

比如图一中的两棵二叉树,虽然它们是不同二叉树,但是它们的先序、后序遍历序列都是相同的。

但是对于“真二叉树”(每个内部节点都有两个孩子的二叉树),给定它的先序、后序遍历序列足以完全确定它的结构。

将二叉树的n个节点用[1, n]内的整数进行编号,输入一棵真二叉树的先序、后序遍历序列,请输出它的中序遍历序列。

输入

第一行为一个整数n,即二叉树中节点的个数。

第二、三行为已知的先序、后序遍历序列。

1 ≤ n ≤1001

输入的序列是{1,2...n}的排列,且对应于一棵合法的真二叉树

输出

仅一行,给定真二叉树的中序遍历序列。

样例输入

5

1 2 4 5 3

4 5 2 3 1

样例输出

4 2 5 1 3

题解

这道题考察了对于二叉树的理解。

难点就在于如何建树(或者按照树的规则扫描也可,下述为建树的方法)。其实我们可以发现,通过先序遍历可以得到左子树根,通过后序遍历可以得到右子树根,同时我们通过子根也可以将先序遍历中的“左子树”和“右子树”迅速找到,右子树亦然。

以上图样例为例,在(1,5,1,5)的区间中(先序左,先序右,后序左,后序右),可以得到1的左子树根为2,右子树根为3,左子树区间为(2,4,1,3),右子树区间为(5,5,4,4),然后递归下去即可。

参考代码

#include<cstdio>
using namespace std;
int p[100000],q[100000];
int n,seg[100000],rev[100000];
struct tree
{
	int l,r,num;
}tr[200000];
void make_tree(int l1,int r1,int l2,int r2)
{
	if(l1==r1) return;
	tr[p[l1]].l=p[l1+1];
	tr[p[l1]].r=q[r2-1];
	make_tree(l1+1,l1+1+rev[p[l1+1]]-l2,l2,rev[p[l1+1]]);
	make_tree(l1+1+rev[p[l1+1]]-l2+1,r1,rev[p[l1+1]]+1,r2-1);
}
void search(int t)//中序遍历
{
	if(!tr[t].l&&!tr[t].r) 
	{
		printf("%d ",t);
		return;
	}
	search(tr[t].l);
	printf("%d ",t);
	search(tr[t].r);
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) 
	{
		scanf("%d",&p[i]);
		seg[p[i]]=i;//记录先序编号
	}
	for(int i=1;i<=n;i++) 
	{
		scanf("%d",&q[i]);
		rev[q[i]]=i;//记录后序编号
	}
	for(int i=1;i<=n;i++) tr[i].l=tr[i].r=0;
	make_tree(1,n,1,n);
	search(p[1]);
	return 0;
}

T4问题 D: 小车问题

题目描述

    甲、乙两人同时从A地出发要尽快同时赶到B地。出发时A地有一辆小车,可是这辆小车除了驾驶员外只能带一人。已知甲、乙两人的步行速度一样,且小于车的速度。问怎样利用小车才能使两人尽快同时到达。

输入

      一行三个数据,分别表示A、B两地的距离s、人的步行速度a和车的速度b。

输出

    两人同时到达B地需要的最短时间,保留小数点后面两位。

样例输入

120 5 25

样例输出

9.60

题解

这道题真没啥好说的,就是道小学奥数题。假设甲先坐车,经过路程x后下车步行到达终点。在甲下车后,司机往回走一段路程接上乙,并携带乙与甲同时到达终点。然后就会列出一个极其复杂的方程,左右两式表示总时间。

假设人速度v1,车速度v2,总路程s。

方程如下:

 解得:

 我们所求时间为:

参考代码

#include<cstdio>
using namespace std;
int main()
{
	double v1,v2,s;
	scanf("%lf%lf%lf",&s,&v1,&v2);
	double x=(v1+v2)*s/(v1*3.0+v2);
	printf("%.2lf",x/v2+(s-x)/v1);
	return 0;
}

 

T5问题 E: 最大整数

题目描述

设有 n 个正整数(n≤20),将它们联接成一排,组成一个最大的多位整数。
例如:n=3 时,3 个整数 13,312,343 联接成的最大整数为:34331213
又如:n=4 时,4 个整数 7,13,4,246 联接成的最大整数为:7424613

输入

第一行一个整数,表示n;
第二行n个整数,之间用一个空格隔开。

输出

一行一个整数,表示联接成的最大多位数。

样例输入

3

13 312 343

样例输出

34331213

题解

对于这道题,很容易想到枚举每一位。

在数的中间某一位,很容易得到最优的一些值,而对于当前的数已经扫描完了,就在剩下的数中选择一个最优的(下述代码中没有实现,只有80%的正确率)。这种选择不容易实现,但是可以先让每一个数位数一样(最高位不足就补0),然后由大到小排序,最后就可以得到100%正确率。

参考代码(不全,请谨慎使用)

#include<cstdio>
#include<cstring>
using namespace std;
struct node
{
	char s[100000];
	int len;
}a[30];
int n,maxn[100000],dp[100000][3],sum1,col[30],cnt=0;
int max1=-1,nxt[100000],pre[100000],head=1;
int max(int p,int q) { return p>q?p:q; }
int main()
{
	scanf("%d",&n);
	memset(maxn,127/3,sizeof(maxn));
	for(int i=1;i<=n;i++)
	{
		scanf("%s",a[i].s);
		a[i].len=strlen(a[i].s);
		sum1+=a[i].len;
		if(a[i].s[0]-'0'>=max1) max1=a[i].s[0]-'0';
	}
	maxn[1]=max1;
	for(int i=1;i<=n;i++)
	{
		if(a[i].s[0]-'0'==max1) 
		{
			dp[++cnt][0]=max1;
			dp[cnt][1]=0;
			dp[cnt][2]=i;
			col[cnt]=1<<(i-1);
			nxt[cnt]=cnt+1;
			pre[cnt]=cnt-1;
		}
	}
	for(int i=2;i<=sum1;i++)
	{
		int tll=head;max1=-1;
		while(tll<=cnt)
		{
			int max2=max1;
			dp[tll][1]++;
			if(dp[tll][1]>=a[dp[tll][2]].len)
			{
				int num,col0=col[tll];
				for(int i1=0;i1<n;i1++)
				{
					if(((col[tll]>>i1)&1)==0)
					{
						if(a[i1+1].s[0]>max1)
						{
							col[tll]=col0|(1<<i1);
							max1=a[i1+1].s[0];
							num=i1+1;
						}
					}
				}
				if(max1<max2) 
				{
					nxt[pre[tll]]=nxt[tll];
					pre[nxt[tll]]=pre[tll];
					if(tll==head) head=nxt[tll];
				}
				else
				{
					max1=max(max1,max2);
					dp[tll][0]=max1;
					dp[tll][1]=0;
					dp[tll][2]=num;
				}
			}
			else
			{
				dp[tll][0]=a[dp[tll][2]].s[dp[tll][1]];
				if(max1<=dp[tll][0]) max1=dp[tll][0];
				else
				{
					nxt[pre[tll]]=nxt[tll];
					pre[nxt[tll]]=pre[tll];
					if(tll==head) head=nxt[tll];
				}
			}
			tll=nxt[tll];
		}
		tll=head;
		while(tll<=cnt)
		{
			if(dp[tll][0]<max1) 
			{
				nxt[pre[tll]]=nxt[tll];
				pre[nxt[tll]]=pre[tll];
				if(tll==head) head=nxt[tll];
			}
			tll=nxt[tll];
		}
		maxn[i]=dp[head][0]-'0';
		if(col[tll]==((1<<n)-1))
			{
				nxt[pre[tll]]=nxt[tll];
				pre[nxt[tll]]=pre[tll];
				if(tll==head) head=nxt[tll];
				tll=nxt[tll];
				continue;
			}
	}
	for(int i=1;i<=sum1;i++) printf("%d",maxn[i]);
	return 0;
}

T6问题 F: 整数区间

题目描述

请编程完成以下任务:
1.读取闭区间的个数及它们的描述;
2.找到一个含元素个数最少的集合,使得对于每一个区间,都至少有一个整数属于该集合,输出该集合的元素个数。

输入

首行包括区间的数目n,1<=n< =10000,接下来的n行,每行包括两个整数a,b,被一空格隔开,0<=a< =b<= 10000,它们是某一个区间的开始值和结束值。

输出

1行一个整数,表示集合元素的个数,对于每一个区间都至少有一个整数属于该区间,且集合所包含元素数目最少。

样例输入

4

3 6

2 4

0 2

4 7

样例输出

2

题解

这是一道贪心题。排序方式多样,本次主要介绍按右端点升序排序的做法。

首先按右端点升序排序,然后从左到右搜索区间,把当前左端点与最大右端点比较,判断有无重合。若有重合,就continue,反之就++cnt,并更新最大右端点。

现在来解释一下为什么这样贪心是正确的。一个区间,如果要取到它,就需要在该区间取点,最优是取一个点。那么取两端和取中间,哪种情况更优呢?,按照以端点排序的顺序,当然是取两端了,因为之前更小的区间已经满足了,现在只需要考虑右端点更大的区间,而显然,取到最大点也就是右端点,自然能使其利用率最高,因此该种贪心是正确的。

参考代码

#include<cstdio>
#include<algorithm>
using namespace std;
struct node
{
	int l,r;
}tr[200100];
bool comp1(node p,node q)
{
	if(p.r<q.r) return 1;
	else return 0;
}
int n,R[20010];
int cnt=0;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&tr[i].l,&tr[i].r);
	}
	sort(tr+1,tr+n+1,comp1);
	R[++cnt]=tr[1].r;
	for(int i=2;i<=n;i++)
	{
		int lx=1,rx=cnt;
		if(tr[i].l>R[cnt]) R[++cnt]=tr[i].r;//判断是否有交集
	}
	printf("%d",cnt);
	return 0;
}

T7问题 G: 最少转弯问题

题目描述

给出一张地图,这张地图被分为 n×m(n,m<=100)个方块,任何一个方块不是平地就是高山。平地可以通过,高山则不能。现在你处在地图的(x1,y1)这块平地,问:你至少需要拐几个弯才能到达目的地(x2,y2)?你只能沿着水平和垂直方向的平地上行进,拐弯次数就等于行进方向的改变(从水平到垂直或从垂直到水平)的次数。例如:如图 ,最少的拐弯次数为 5。

 

输入

第 1 行:n m
第2 至 n+1 行:整个地图地形描述(0:空地;1:高山),如图:
第 2 行地形描述为:1 0 0 0 0 1 0
第 3 行地形描述为:0 0 1 0 1 0 0
……
第 n+2 行:x1 y1 x2 y2(分别为起点、终点坐标)

输出

一行一个整数 s(即最少的拐弯次数)。

样例输入

5 7

1 0 0 0 0 1 0

0 0 1 0 1 0 0

0 0 0 0 1 0 1

0 1 1 0 0 0 0

0 0 0 0 1 1 0

1 3 1 7

样例输出

5

题解

这是一道搜索题,关键在于记忆化和更新答案优化。注意边界条件。

参考代码

#include<cstdio>
using namespace std;
int dx[5]={0,0,1,0,-1};
int dy[5]={0,1,0,-1,0};
int n,m,e_map[101][101],st_x,st_y,ed_x,ed_y;
int cnt=999999999,vis[101][101];
bool pd(int x,int y)
{
	return (x<=n)&&(x>0)&&(y<=m)&&(y>0);
}
void dfs(int x,int y,int d,int dep)
{
	if(x==ed_x&&y==ed_y) 
	{
		if(dep<cnt) cnt=dep;
		return;
	}
	for(int i=1;i<=4;i++)
	{
		int x1=x+dx[i];
		int y1=y+dy[i];
		if(pd(x1,y1)&&!vis[x1][y1]&&!e_map[x1][y1])
		{
			vis[x1][y1]=1;
			if(d==i) dfs(x1,y1,d,dep);
			else dfs(x1,y1,i,dep+1);
			vis[x1][y1]=0;
		}
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	  for(int j=1;j<=m;j++)
	    scanf("%d",&e_map[i][j]);
	scanf("%d%d%d%d",&st_x,&st_y,&ed_x,&ed_y);
	vis[st_x][st_y]=1;
	for(int i=1;i<=4;i++)
	{
		if(pd(st_x,st_y))
		  dfs(st_x,st_y,i,0);
	}
	printf("%d",cnt);
	return 0;
}

T8问题 H: 单词的划分

题目描述

有一个很长的由小写字母组成字符串。为了便于对这个字符串进行分析,需要将它划分成若干个部分,每个部分称为一个单词。出于减少分析量的目的,我们希望划分出的单词数越少越好。你就是来完成这一划分工作的。

输入

第一行,一个字符串。(字符串的长度不超过 100);
第二行一个整数 n,表示单词的个数。(n<=100)第 3~n+2 行,每行列出一个单词。

输出

一个整数,表示字符串可以被划分成的最少的单词数。

样例输入

realityour

5

real

reality

it

your

our

样例输出

2

题解

这道题只要求单词数,ans只是一个值,因此考虑dp。现在来看我们的dp顺序,还是从头扫一遍,枚举每一个字母是否可以作为某一个单词的末端,然后往前枚举,看是否与该单词符合,最后累加dp值,再求一个最小值,因此我们的初值应赋值为无穷大,dp[0]=0,最后答案就是dp[len(s)],s为字符串。

参考代码

#include<cstdio>
#include<cstring>
using namespace std;
struct node
{
	char s[200];
	int len;
};
node pk[200],st;
int n,vis[200][200],dp[200];
int min1(int p,int q) { return p<q?p:q; }
int main()
{
	memset(dp,127/3,sizeof(dp));
	scanf("%s",st.s);
	st.len=strlen(st.s);
	scanf("%d",&n);
	for(int i=1;i<=n;i++) 
	{
		scanf("%s",pk[i].s);
		pk[i].len=strlen(pk[i].s);
		vis[pk[i].s[pk[i].len-1]][++vis[pk[i].s[pk[i].len-1]][0]]=i;
	}
	dp[0]=0;
	for(int i=0;i<st.len;i++)
	{
		for(int j=1;j<=vis[st.s[i]][0];j++)
		{
			int pd=0;
			int num=vis[st.s[i]][j];
			if(i<pk[num].len-1) continue;
			for(int k=i;k>=i-pk[num].len+1;k--)
			{
				if(st.s[k]!=pk[num].s[pk[num].len-i+k-1])
				{
					pd=1;break;//单词不正确,break
				}
			}
			if(pd==0) dp[i+1]=min1(dp[i+1],dp[i-pk[num].len+1]+1);
		}
	}
	printf("%d",dp[st.len]);
	return 0;
}

T9问题 I: 众数

题目描述

 N 个 1 到 30000 间无序数正整数,其中 1≤N≤10000,同一个正整数可能会出现多次,出现次数最多的整数称为众数。求出它的众数及它出现的次数。

输入

若干行,每行两个数,第 1 个是众数,第 2 个是众数出现的次数。

样例输入

12

2 4 2 3 2 5 3 7 2 3 4 3

样例输出

2  4

3  4

题解

这道题数据范围并不大,所以可以考虑桶排序O(n)求出所有答案,注意众数可能不止一个,这道题有特殊要求,中间有2个空格,特别需要注意!

参考代码

#include<cstdio>
using namespace std;
int vis[100000],n,maxn,num[100000];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int u;scanf("%d",&u);
		vis[u]++;
		if(vis[u]>maxn) 
		maxn=vis[u];
	}
	for(int i=1;i<=30000;i++)
	if(vis[i]==maxn)
	printf("%d  %d\n",i,maxn);
	return 0;
} 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值