ACM 1023-1027 1058

HDOJ-ACM 1023-1027 1058解答及思路

1023

Problem Description

Input

Output

Sample Input

Sample Output

题目要求

基于1022题,在它的基础上要求输出小于100辆火车进站后的出站顺序。

参考AC代码(一)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<stdio.h>
#include<iostream>
#include<memory.h>
using namespace std;
#define MAX 101
#define BASE 10
void multiply(int a[],int len,int b)
{
for(int i=len-1,carry=0;i>=0;--i)
{
carry+=b*a[i];
a[i]=carry%BASE;
carry/=BASE;
}
}
void divide(int a[],int len,int b)
{
for(int i=0,div=0;i<len;++i)
{
div=div*BASE+a[i];
a[i]=div/b;
div%=b;
}
}
int main()
{
int i,j,h[101][MAX];
memset(h[1],0,MAX*sizeof(int));
for(i=2,h[1][MAX-1]=1;i<=100;++i)
{
memcpy(h[i],h[i-1],MAX*sizeof(int));
multiply(h[i],MAX,4*i-2);
divide(h[i],MAX,i+1);
}
while(cin>>i)
{
for(j=0;j<MAX && h[i][j]==0;++j);
for(;j<MAX;++j)
cout<<h[i][j];
cout<<endl;
}
return 0;
}

参考AC代码(二)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<bits/stdc++.h>
#define MAX 101
#define BASE 10000
using namespace std;
void multiply(int a[],int len,int b){
for(int i=len-1,carry=0;i>=0;--i){
carry+=b*a[i];
a[i]=carry%BASE;
carry/=BASE;
}
}
void divide(int a[],int len,int b){
for(int i=0,div=0;i<len;++i){
div=div*BASE+a[i];
a[i]=div/b;
div%=b;
}
}
int main(){
int i,j,h[101][MAX];
memset(h[1],0,MAX*sizeof(int));
for(i=2,h[1][MAX-1]=1;i<=100;++i){
memcpy(h[i],h[i-1],MAX*sizeof(int));
multiply(h[i],MAX,4*i-2);
divide(h[i],MAX,i+1);
}
while(cin>>i){
for(j=0;j<MAX && h[i][j]==0;++j);
printf("%d",h[i][j++]);
for(;j<MAX;++j) printf("%04d",h[i][j]);
printf("\n");
}
return 0;
}

思路

  首先,我们假设f(n)是序列个数为n的出栈序列种数。(我们假定,最后出栈的元素为k,k取不同值时的情况是相互独立的,也就是求出每种k最后出栈的情况数后可
用加法原则,由于k最后出栈,因此,在k入栈之前,比k小的值均出栈,此处情况有f(k-1)种,而之后比k大的值入栈,且都在k之前出栈,因此有f(n-k)种方式,由于比k
小和比k大的值入栈出栈情况是相互独立的,此处可用乘法原则,f(n-k)f(k-1)种,求和便是Catalan递归式。而该题要用到Catalan的递归式:h(n)=h(n-1)(4*n-2)/(n+1);
而该题最大求到了h(100),已经超过了50位且接近60位数,所以long long型也无法使用,要使用大数的乘法和除法。代码一是以10为基准教容易理解。而代码二是以
10000为基准,即数组的一个位置可存放四位数,把四位数作为一个整体进行计算。此题的乘除法也设计的极为巧妙,大大减少了运算复杂度。

注意事项

  开始需要把h[1]的那一行全部初始化为0,再用memset时确保每一行没有用到的位置全部置为0。在使用10000为基准的时候要注意一次性输出4位数,若不满4位数则要
向左用0补齐。在使用除法运算的时候,若有一位没有除尽,则要把余数*基数加上后一位的数字,继续进行除法运算。若基数取的是10,则进行的是我们熟悉的运算,容易
理解。但相应的也会增加程序的运行时间。

转跳 Catalan number

1024

Problem Description

Input

Output

Sample Input

Sample Output

题目要求

给定n个数,和m个段。求在这n个数中求出m个子段,使得这些子段的和最大.

参考AC代码(一)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include<stdio.h>
#include<iostream>
using namespace std;
const int MAX=1000001;
int dp[2][MAX];
int w[MAX];
int sum[MAX];//在主函数里开个sum[MAX],是不行的,因为MAX是在太大!
int cmax(int a,int b)//求最大值
{
return a>b?a:b;
}
int main()
{
int i,k;
int m,n;
while(scanf("%d%d",&m,&n)>0)
{
sum[0]=0;
for(i=1;i<=n;i++)
{
cin>>k;
sum[i]=sum[i-1]+k;//sum[i]里存的是前i个元素的和
dp[0][i]=0;//从前i个元素中取0段,最大值为0
}
//我们假设a[i]中存放该序列第i个值,w[i][k]表示前k个数分为i段,第k个数必须选这种情况下取得的最大值
//b[i][k]表示在前k个数中取i段这种情况下取得的最大值
//w[i][k]:前k个数分为i段,第k个数必须选;1:第k个数单独为1段;2:第k个数与前面的数连一块。w[i][k]=max(b[i-1][k-1],w[i][k-1])+a[k];
//b[i][k]:前k个数分为i段,第k个数可选可不选;1:选第k个数,2:不选第k个数。b[i][k]=max(b[i][k-1],w[i][k])
//w[i][k]=max(b[i-1][k-1],w[i][k-1])+a[k];
//b[i][k]=max(b[i][k-1],w[i][k]);
//w[i][k],b[i][i]容易求得,所以由b[i-1][k-1]->w[i][k]->b[i][k],只要知道b[0][k],全部都能成功运行!
//当从k个元素中取j段,可以分为两种情况,即第k个元素可以取,也可以不取,取,那么a[k]要么是单独为一段b[i-1][k-1]+a[k];
//要么是第k个数与前面的数连一块,即w[i][k-1]+a[k],故w[i][k]=max(b[i-1][k-1],w[i][k-1])+a[k];
//要么不取 即b[i][k]=b[i][k-1];
//综合起来,b[i][k]=max(b[i][k-1],w[i][k]);
int t=1;
for(i=1;i<=m;i++)//i表示在取i段,自然i<=m;
{
for(k=i;k<=n;k++)//为什么k从i开始?dp[i][k](k<i)是没有意义的!
{
if(i==k)
dp[t][k]=w[k]=sum[k];//从k个数中取k段的最大值是前k个数的和
else
{
w[k]=cmax(dp[1-t][k-1],w[k-1])+sum[k]-sum[k-1];//w[k]表示k个元素取i段,a[k]必须取时的最大值
//w[i][k]=max(b[i-1][k-1],w[i][k-1])+a[k];
dp[t][k]=cmax(dp[t][k-1],w[k]);//dp[t][k]表示在a[k]可取可不取这两种情况下取得的最大值
//自然,dp[t][k]记录的就是在前k个元素中取i段时取得的最大值!
}
}
t=1-t;//t在1,0之间交替变换
//为什么要交替呢?这是为了节省空间
//仔细观察递归式
//w[i][k]=max(b[i-1][k-1],w[i][k-1])+a[k];
//b[i][k]=max(b[i][k-1],w[i][k]);
//我们发现,对于取i段,w[i][j]只与b[i-1][k-1]和w[i][k-1]有关,与之前的那一些项没有关系
//因此我们数组可以开小一点,用更新来覆盖掉前面的值!
}
cout<<dp[m%2][n]<<endl;//奇次轮还是偶次轮
}
return 0;
}

参考AC代码(二)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include<stdio.h>
#include<cstring>
#include<algorithm>
using namespace std;
int curr_best[1000010];//保存当前状态
int prev_best[1000010];
int max_sum,i,j;
int n,m;
int data[1000010];
#define MIN_SUM 0x80000000 //在4字节中转换成10进制是-2147483648,最小的二进制数
int main()
{
while(scanf("%d %d",&m,&n)!=EOF)
{
for(int i=1;i<=n;i++)
scanf("%d",&data[i]);
memset(curr_best,0,sizeof(curr_best));
memset(prev_best,0,sizeof(prev_best));
int j=0;
for(int i=1;i<=m;++i)
{
max_sum=MIN_SUM;
for(j=i;j<=n;++j)
{
//curr b(i,*);
//prev b(i-1,*);
curr_best[j]=max(curr_best[j-1],prev_best[j-1])+data[j];
prev_best[j-1]=max_sum;//这两条语句不能写反了,这块我纠结了好久,解释一下,prev_best[j-1]表示的是上一个状态中i...j-1的最大值,max_sum更新之后表示的i...j的最大值,所以不能写反了
max_sum=max(max_sum,curr_best[j]);
}
prev_best[j-1]=max_sum;//prev_best[j-1]中始终保持着前一个状态的最大值,这个很重要
}
printf("%d\n",max_sum);
}
return 0;
}

思路

见代码注释
代码(二)思路与(一)相同,只是处理的方法略有不同。

注意事项

由于这题给的n很大,需要用dp来解。而且需要优化操作。

1025

Problem Description

Input

Output

Sample Input

Sample Output

题目要求

两条线,每条线上各有n个点,表示n个城市,第一条表示rich 城市,第二天表示poor 城市,从第一条线连接到第二条线上,问最多能连多少条不相交的路线。

参考AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<stdio.h>
#include<iostream>
#include<string>
using namespace std;
int num[50001],ans[50001];
int main()
{
int n,temp1,temp2,k=1,height,low,mid,len;
while(scanf("%d",&n)!=EOF)
{
for(int i=0;i<n;i++)
{
scanf("%d %d",&temp1,&temp2);
num[temp1]=temp2;
}
memset(ans,0,sizeof(ans));
ans[1]=num[1],len=1;
for(int i=2;i<=n;i++)
{
low=1;
height=len;
while(height>=low)
{
mid=(height+low)/2;
if(num[i]>ans[mid])
low=mid+1;
else
height=mid-1;
}
ans[low]=num[i];
if(low>len)
len++;
}
cout<<"Case "<<k<<":"<<endl;
if(len==1)
cout<<"My king, at most 1 road can be built."<<endl;
else
cout<<"My king, at most "<<len<<" road can be built."<<endl;
cout<<endl;
k++;
}
return 0;
}

思路

LIS算法:可以按照富有(贫穷)的排序,然后找贫穷(富有)的村子序列的最长上升子序列就可以了,这样就能保证不相交,而且就是最优解。

注意事项

因为数据大,n^2的算法是不可以的,需要使用n×logn的算法形式。需要注意road有单复数形式。可以发现B插入数据是有序的,而且进行替换而不需要移动,
也就是说可以使用二分查找,时间复杂度就降下来了。这种算法只能得到最终的数据个数,但是如果需要得到具体的内容就不行了。看网上说首先要排序,
其实仔细看看题目的说明,它的边的输入是有序的,所以根本不需要进行排序。

1026

Problem Description

Input

Output

Sample Input

Sample Output

题目要求

迷宫问题。要求从左上走到右下并记录下实时的数据。

参考AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#include<stdio.h>
#include<iostream>
#include<queue>
#include<stack>
using namespace std;
const int SIZE=120;
struct Record
{
int i;
int j;
int cost; //总耗时
bool operator<(const Record &a) const
{
return cost>a.cost;
}
}temp,temp1;
struct node
{
int i;
int j;
int num;
}pre[120][120]; //前驱结点
int mg[SIZE][SIZE]; //定义一个迷宫数组
int b[4][2]={{0,1},{0,-1},{1,0},{-1,0}};
int m,n; //迷宫大小,行列值
int mins;
bool bfs(int x1,int y1,int x2,int y2)
{
priority_queue<Record> q;
int bx,by;
int flag[SIZE][SIZE]; //标志数组最好放进来,这样每次调用函数时重新建立,会将里面的数据清0;
memset(flag,0,sizeof(flag)); //在使用flag数组的时候,先清一下0,不然会WA
flag[x1][y1]=1; //走过得路径标记为1,表示不能再走
temp1.i=x1;
temp1.j=y1;
temp1.cost=0; //起点的耗时为0
q.push(temp1); //入队列
while(!q.empty())
{
temp=q.top(); //取队顶元素
q.pop(); //出队列
if(temp.i==x2 && temp.j==y2)
{
mins=temp.cost; //直到终点的花费就是最少花费
return true;
}
for(int i=0;i<4;i++)
{
bx=temp.i+b[i][0];
by=temp.j+b[i][1];
if(bx>=0 && bx<m && by>=0 && by<n && !flag[bx][by] && mg[bx][by]!=-1)
{
temp1.i=bx;
temp1.j=by;
temp1.cost=temp.cost+mg[bx][by];
pre[bx][by].i=temp.i; //bx,by是现在结点的下标值
pre[bx][by].j=temp.j; //用来记录前驱结点的下标值
pre[bx][by].num=mg[temp.i][temp.j];
q.push(temp1); //入队列!
flag[bx][by]=1; //标记为1;走过了之后就不能再走了!
}
}
}
return false;
}
void print()
{
int k=1,i;
int x1,y1;
int x2,y2;
stack<node> s;
node temp,temp1;
temp1.i=m-1; temp1.j=n-1; temp1.num=mg[m-1][n-1];
s.push(temp1);
for(x1=m-1,y1=n-1;;) //从终点开始,一级级地向前压栈
{
x2=x1;
y2=y1;
s.push(pre[x2][y2]); //不断的压栈
x1=pre[x2][y2].i; //不断地向前搜寻前结点
y1=pre[x2][y2].j; //一直压到了x1=0,y1=0;
if(x1==0 && y1==0) break;
}
while(!s.empty())
{
if(s.size()-1==0 && s.top().num==1) break;
temp=s.top();
s.pop();
if(s.size()>0)
temp1=s.top();
if(temp.num==1)
printf("%ds:(%d,%d)->(%d,%d)\n",k++,temp.i,temp.j,temp1.i,temp1.j);
else
{
for(i=1;i<temp.num;i++)
printf("%ds:FIGHT AT (%d,%d)\n",k++,temp.i,temp.j);
if(s.size()>0)
printf("%ds:(%d,%d)->(%d,%d)\n",k++,temp.i,temp.j,temp1.i,temp1.j);
}
}
}
int main()
{
int i,k,x1,y1;
char str[SIZE]; //字符串数组
while(scanf("%d %d",&m,&n)>0)
{
for(i=0;i<m;i++)//m行
{
cin>>str; //输入字符串
for(k=0;k<n;k++)//n列
{
if(str[k]=='.') mg[i][k]=1;
else if(str[k]=='X') mg[i][k]=-1;
else mg[i][k]=str[k]-'0'+1;
}
}
if(bfs(0,0,m-1,n-1))
{
printf("It takes %d seconds to reach the target position, let me show you the way.\n",mins);
print();
}
else
printf("God please help our poor hero.\n");
printf("FINISH\n");
}
return 0;
}

思路

使用广搜+优化队列可以使得到的第一个路线就是最优化路线。mg数组中.存放的为1,X存放的为-1,怪物需要消耗几秒就存储几秒。flag数组用来存放走过的位置,
若走过的话存储为1,不能再走。使用优化队列,每次进行判断的都是队首的元素,如果满足继续放入队首,继续对改元素进行下一步判断。若不满足则退出队列,
第二个元素再进行判断,依次重复最后得到最优解。再使用栈进行输出。从末尾开始反向把元素一一压入栈中,再利用栈的特点从正向开始输出。pre数组存储的是
每个满足题意的坐标用于输出。

注意事项

flag数组需要初始化为0,输出的时候遇到怪物的情况需要额外讨论。本题若使用深搜会超时,需要使用广搜+优化队列求解。把自己定义的结构体变量放到自己建立的
队列或者栈中时需要重载小于号。
本题使用了queue和stack的头文件:
(1)queue:若定义队列queue q,q.empty()判断是否为空队列,q.push(1)表示元素1进队列,q.pop()表示出队列,q.front()表示得到队首的值,q.size()表示得
到队列里元素个数。
(2)stack:若定义栈stackq,q.empty()判断是否为栈空,q.push(1)表示元素1进栈,q.pop()表示弹出栈中元素,q.top()表示取出栈顶元素,q.size()表示得到栈
中的元素个数。

1027

Problem Description

Input

Output

Sample Input

Sample Output

题目要求

按照字典顺序输出从1-n n个自然数的组合数。

参考AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int SIZE=1002;
int main()
{
int n,m;
int i,count;
int seq[SIZE];
while(scanf("%d %d",&n,&m)!=EOF)
{
for(i=0;i<SIZE;i++)
seq[i]=i+1;
count=0;
do
{
count++;
if(count==m)
{
for(i=0;i<n;i++)
if(i==n-1)
cout<<seq[i];
else
cout<<seq[i]<<" ";
cout<<endl;
break;
}
}
while (next_permutation(seq,seq+n));
}
return 0;
}

思路

本题若使用如上代码的STL全排列函数,则可以快速求解,它的作用就是求出下一个字典序的组合数。与之对应的有prev_permutation,作用是求出上一个字典序
的组合数。但是它并没有真正带给我们实质的知识点,所以下面给出不使用STL的求解方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<stdipo.h>
#include<iostream>
using namespace std;
#define MAX 50
int a[MAX];
int permutation(int n) //排列函数
{
int i,j,tmp,flag=1;
for(i=n;i>=2 && flag;i--)
if(a[i]>a[i-1]) //从最后每相邻的两个进行比较,如果有前面一个比后面的小(i为较小位置,ii,为较大位置),那么此时一定存在一个排列比当前的大
{
for(j=n;j>=2 && flag;j--) //应该找这个较小的数的后面从最后开始比它大的第一个数
{
if(a[j]>a[i-1]) //将它换到当前较小的位置上
{
tmp=a[j]; a[j]=a[i-1]; a[i-1]=tmp; flag=0;
}
}
if(!flag) //较大数到最后进行逆序,即交换,这样就产生了下一个排列,原理类似于,找到下一个较大的作为开始位的数,然后将后面的数字从最小开始即升序
{
tmp=a[i]; a[i]=a[n]; a[n]=tmp;
}
}
if(flag)
return 0;
else
return 1;
}
int main()
{
int n,i;
while(1)
{
cin>>n;
for(i=1;i<50;i++) a[i]=i;
do
{
for(i=1;i<=n;i++) cout<<a[i]<<" ";
cout<<endl;
}while(permutation(n));//一直产生排列,直到逆序数<按照上面的方式>(按从大到小)数为0;
}
return 0;
}

以上代码的思路为:
从末尾开始判断前一个元素是否小于后一个元素,若找不到向前推动搜寻数组的位置,若能找到,则说明可以找到比改数大的一个组合数。那么再定义j从末尾开始与i-1的大
小,若a[i-1]<a[j],那么交换两数的位置后再交换a[i]与a[n]的数值以确保大数排到了后面,一直执行permutation函数到满足题意。

注意事项

每行的末尾没有空格,所以需要讨论输出格式。

1058

Problem Description

Input

Output

Sample Input

Sample Output

题目要求

定义了一个humble numbe,并找出第N个humble number。定义为:除了1以外仅有2,3,5,7一个或多个因子的数。

参考AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
int n;
typedef long long LL;
LL data[5843];
void init()
{
int i=1,j=1,m=1,n=1;
int index=1;
data[index]=1;
while(1)
{
if(index==5843)
break;
LL temp1=min(data[i]*2,data[j]*3);
LL temp2=min(data[m]*5,data[n]*7);
LL res=min(temp1,temp2);
if(res==data[i]*2)
i++;
if(res==data[j]*3)
j++;
if(res==data[m]*5)
m++;
if(res==data[n]*7)
n++;
if(res!=data[index])
data[++index]=res;
}
}
int main()
{
init();
while(cin>>n && n!=0)
{
if(n%10==1 && n%100!=11)
cout<<"The "<<n<<"st humble number is "<<data[n]<<"."<<endl;
else if(n%10==2 && n%100!=12)
cout<<"The "<<n<<"nd humble number is "<<data[n]<<"."<<endl;
else if(n%10==3 && n%100!=13)
cout<<"The "<<n<<"rd humble number is "<<data[n]<<"."<<endl;
else
cout<<"The "<<n<<"th humble number is "<<data[n]<<"."<<endl;
}
return 0;
}

思路

先用data数组存放1-5842的humble number,接着判断n的末尾用哪种输出格式。data存放humble number的原理为:用这4个因子分别乘1,接着找出最小的数并判断是
属于哪个因子的,之后该因子的乘数+1,若res是一个新数,那么存入data数组,index再指向下一个位置,若index到了5843结束循环。

注意事项

在英语中11,12,13由于有对应的单词,所以不使用st,nd,rd,需要特别注意。另外此题需要用long long来储存数组,由于方便起见,可以用typedef把long long重新
定义为LL,可以节省书写速度。

文章目录
  1. 1. HDOJ-ACM 1023-1027 1058解答及思路
    1. 1.1. 1023
      1. 1.1.1. 题目要求
      2. 1.1.2. 参考AC代码(一)
      3. 1.1.3. 参考AC代码(二)
      4. 1.1.4. 思路
      5. 1.1.5. 注意事项
      6. 1.1.6. 转跳 Catalan number
    2. 1.2. 1024
      1. 1.2.1. 题目要求
      2. 1.2.2. 参考AC代码(一)
      3. 1.2.3. 参考AC代码(二)
      4. 1.2.4. 思路
      5. 1.2.5. 注意事项
    3. 1.3. 1025
      1. 1.3.1. 题目要求
      2. 1.3.2. 参考AC代码
      3. 1.3.3. 思路
      4. 1.3.4. 注意事项
    4. 1.4. 1026
      1. 1.4.1. 题目要求
      2. 1.4.2. 参考AC代码
      3. 1.4.3. 思路
      4. 1.4.4. 注意事项
    5. 1.5. 1027
      1. 1.5.1. 题目要求
      2. 1.5.2. 参考AC代码
      3. 1.5.3. 思路
      4. 1.5.4. 注意事项
    6. 1.6. 1058
      1. 1.6.1. 题目要求
      2. 1.6.2. 参考AC代码
      3. 1.6.3. 思路
      4. 1.6.4. 注意事项
|