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;
}