ACM实习

ACM程序设计实践
课程设计与实习报告

目录
1.第一部分(必做题) 1
1.1第一题 1
1.1.1题目内容 1
1.1.2题目要求 1
1.1.3设计思想 1
1.1.4算法分析 1
1.1.5核心代码 1
1.1.6 测试数据或截图 2
1.2第二题 2
1.2.1题目内容 2
1.2.2题目要求 3
1.2.3设计思想 3
1.2.4算法分析 3
1.2.5核心代码 3
1.2.6 测试数据或截图 4
1.3第三题 4
1.3.1题目内容 4
1.3.2题目要求 4
1.3.3设计思想 4
1.3.4算法分析 5
1.3.5核心代码 5
1.3.6 测试数据或截图 5
1.4第四题 6
1.4.1题目内容 6
1.4.2题目要求 6
1.4.3设计思想 6
1.4.4算法分析 6
1.4.5核心代码 7
1.4.6 测试数据或截图 8
1.5第五题 8
1.5.1题目内容 8
1.5.2题目要求 8
1.5.3设计思想 8
1.5.4算法分析 9
1.5.5核心代码 9
1.5.6 测试数据或截图 10
1.6第六题 10
1.6.1题目内容 10
1.6.2题目要求 10
1.6.3设计思想 11
1.6.4算法分析 11
1.6.5核心代码 11
1.6.6 测试数据或截图 12
1.7第七题 12
1.7.1题目内容 12
1.7.2题目要求 13
1.7.3设计思想 13
1.7.4算法分析 13
1.7.5核心代码 13
1.7.6 测试数据或截图 15
1.8第八题 16
1.8.1题目内容 16
1.8.2题目要求 16
1.8.3设计思想 16
1.8.4算法分析 17
1.8.5核心代码 17
1.8.6 测试数据或截图 18
2.第二部分(若没有做则可以不写) 18
2.1第一题 18
2.1.1题目内容 18
2.1.2题目要求 19
2.1.3设计思想 19
2.1.4算法分析 19
2.1.5核心代码 19
2.1.6 测试数据或截图 20
2.2第二题 20
2.2.1题目内容 20
2.2.2题目要求 20
2.2.3设计思想 21
2.2.4算法分析 21
2.2.5核心代码 21
2.2.6 测试数据或截图 22
3.第二部分(ACM实践必做题) 22
PKU JUDGE ONLINE账号:318202051219 22
3.1第一题(1004) 22
3.1.1题目内容 22
3.1.2题目要求 23
3.1.3设计思想 23
3.1.4算法分析 23
3.1.5核心代码 23
3.1.6 测试数据或截图 23
3.2第二题(1012) 24
3.2.1题目内容 24
3.2.2题目要求 25
3.2.3设计思想 25
3.2.4算法分析 25
3.2.5核心代码 25
3.2.6 测试数据或截图 26
3.3第三题(1013) 26
3.3.1题目内容 26
3.3.2题目要求 27
3.3.3设计思想 27
3.3.4算法分析 28
3.3.5核心代码 28
3.3.6 测试数据或截图 30
3.4第四题(1014) 30
3.4.1题目内容 30
3.4.2题目要求 31
3.4.3设计思想 31
3.4.4算法分析 31
3.4.5核心代码 31
3.4.6 测试数据或截图 33
3.5第五题 33
3.5.1题目内容 33
3.5.2题目要求 34
3.5.3设计思想 34
3.5.4算法分析 34
3.5.5核心代码 34
3.5.6 测试数据或截图 35
4.心得体会 36

1.第一部分(必做题)
1.1第一题
1.1.1题目内容
考虑如下的序列生成算法:从整数 n 开始,如果 n 是偶数,把它除以 2;如果 n 是奇数,把它乘 3 加1。用新得到的值重复上述步骤,直到 n = 1 时停止。例如,n = 22 时该算法生成的序列是:22,11,34,17,52,26,13,40,20,10,5,16,8,4,2,1。人们猜想(没有得到证明)对于任意整数 n,该算法总能终止于 n = 1。这个猜想对于至少 1 000 000内的整数都是正确的。对于给定的 n,该序列的元素(包括 1)个数被称为 n 的循环节长度。在上述例子中,22 的循环节长度为 16。输入两个数 i 和 j,你的任务是计算 i 到 j(包含 i 和 j)之间的整数中,循环节长度的最大值。
1.1.2题目要求
【输入】
输入每行包含两个整数 i 和 j。所有整数大于 0,小于 1000000。
【输出】
对于每对整数 i 和 j,按原来的顺序输出 i 和 j,然后输出二者之间的整数中的最大循环节长度。这三个整数应该用单个空格隔开,且在同一行输出。对于读入的每一组数据,在输出中应位于单独的一行。
1.1.3设计思想
通过比较i~j 之间的循环节长度,通过不断修改当前最大值,最终可以输出二者之间的最大循环节长度。
在这里插入图片描述
图1-1 程序流程图

1.1.4算法分析
时间复杂度为O(1)。
1.1.5核心代码

#include<stdio.h>
int main()
{
	int i,j,n;
	int t=1,int k=1;
	scanf("%d %d",&i,&j);
	while (i!=0&&j!=0)
	{
	for(n=i;n<=j;n++)
	{
		int m = n;//n会持续变化
		k = 1;//k需要从新计数
		while(m!=1)
		{
			if(m%2==0)
				m=m/2;
			else
				m=(m*3)+1;
			k++;
		}
		if(k>t) t=k;
	}
	printf("%d %d %d\n",i,j,t);
	t=0;//t重新归0计数
	scanf("%d %d",&i,&j);//实现循环输入
	}
	return 0;
}

在这里插入图片描述
1.1.6 测试数据或截图

图1-2 程序运行截图
1.2第二题
1.2.1题目内容
17世纪法国数学家加斯帕在《数学的游戏问题》中讲的一个故事:n个教徒和n个非教徒在深海上遇险,必须将一半的人投入海中,其余的人才能幸免于难,于是想了个办法:2n个人围成一个圆圈,从第一个人开始依次循环报数,每数到第九个人就将他扔入大海,如此循环直到仅剩n个人为止 。问怎样的排法,才能使每次投入大海的都是非教徒。
1.2.2题目要求
【输入】
输入文件由一行构成,就是n的值。
【输出】
输出文件中是一行字符串,字符串由n个‘@’字符(代表教徒)和n个‘+’ 字符(代表非教徒)排列构成。该排列使得按照前面的约定每次投入大海的都是非教徒。
1.2.3设计思想
假设所有人都是‘@’(教徒),当数到第九个人(都是数教徒)时把他变成‘+’(非教徒),然后人数减一,当数到表尾时数组下标置零重新开始计数。
在这里插入图片描述
图1-3 程序流程图
1.2.4算法分析
时间复杂度为O(n)。
1.2.5核心代码

#include<stdio.h>
#include<string.h>
int main()
{
	char a[10000];
	int i,m,n;
	scanf("%d",&n);
	for(i=0;i<2*n;i++)
		a[i]='@';//数组全部假设为教徒
	m=2*n;//总人数
	a[m]='\0';//数组最后一个为结束符
	int t=0;//计数器
		for(i=0;m>n;i++)
		{
			if(i==2*n) i=0;//当数到表尾时下标置零,重新开始
			if(a[i]=='@')
				t++;//如果为教徒时则跳过
			if(t==9) 
			{
				a[i]='+';
				m--;//人数减一
				t=0;//计数器置零
			}
		}
	for(i=0;i<2*n;i++)
		printf("%c",a[i]);
	printf("\n");
	return 0;
}

1.2.6 测试数据或截图
在这里插入图片描述
图1-4 程序运行截图
1.3第三题
1.3.1题目内容
交换瓶子
有N个瓶子,编号 1 ~ N,放在架子上。比如有5个编号瓶子:2 1 3 5 4。要求每次拿起2个瓶子,交换它们的位置。经过若干次后,使得瓶子的序号为:1 2 3 4 5。对于这么简单的情况,显然,至少需要交换2次就可以复位。如果瓶子更多呢?你可以通过编程来解决。
1.3.2题目要求
【输入】
输入格式为两行:第一行: 一个正整数N(N<10000), 表示瓶子的数目。第二行:N个正整数,用空格分开,表示瓶子目前的排列情况。
【输出】
输出数据为一行一个正整数,表示至少交换多少次,才能完成排序。
1.3.3设计思想
因为瓶子是有序瓶,它所在的位置与它的编号一致,所以当数组下标与目前它所在位置不一致,则放到它所在位置,移动次数加一。
在这里插入图片描述
图1-5 程序流程图
1.3.4算法分析
时间复杂度为O(n)。
1.3.5核心代码

#include<stdio.h>
#define N 10000
int main()
{
	int i,n,t;//i是循环,n是瓶子数,t是交换中间值
	int count=0;//计数器
	int a[N];
	scanf("%d",&n);
	for(i=1;i<=n;i++)
		scanf("%d",&a[i]);
	for(i=1;i<=n;i++)
	{
		while(a[i] != i)//当瓶子序号是位置不匹配时
		{
			t=a[a[i]];//找到所匹配的位置
			a[a[i]]=a[i];//交换他们的值
			a[i]=t;
			count++;//计数器加1
		}
	}
	printf("%d\n",count);

}

1.3.6 测试数据或截图
在这里插入图片描述
图1-6 程序运行截图
在这里插入图片描述
图1-7 程序运行截图
1.4第四题
1.4.1题目内容
小明几乎每天早晨都会在一家包子铺吃早餐。他发现这家包子铺有N种蒸笼,其中第i种蒸笼恰好能放Ai个包子。每种蒸笼都有非常多笼,可以认为是无限笼。每当有顾客想买X个包子,卖包子的大叔就会迅速选出若干笼包子来,使得这若干笼中恰好一共有X个包子。比如一共有3种蒸笼,分别能放3、4和5个包子。当顾客想买11个包子时,大叔就会选2笼3个的再加1笼5个的(也可能选出1笼3个的再加2笼4个的)。当然有时包子大叔无论如何也凑不出顾客想买的数量。比如一共有3种蒸笼,分别能放4、5和6个包子。而顾客想买7个包子时,大叔就凑不出来了。小明想知道一共有多少种数目是包子大叔凑不出来的。
1.4.2题目要求
【输入】
第一行包含一个整数N。(1 <= N <= 100)
以下N行每行包含一个整数Ai。(1 <= Ai <= 100)
【输出】
一个整数代表答案。如果凑不出的数目有无限多个,输出INF。
1.4.3设计思想
通过分析可知,当每个笼子可以放下的包子数的最大公约数不为一时,凑不出的数目有无限个;当为一时,通过初始化数组为0,第一个位置为1代表可以卖得出,通过递归算出所有可以卖得出的,数组赋值为1,最后统计为0的点即为凑不出的数目。
在这里插入图片描述
图1-8 程序流程图
1.4.4算法分析
时间复杂度为O(n)。
1.4.5核心代码

#include<stdio.h>
int gcd(int a,int b)
{
	if(b==0) 
		return a;
	else
		return gcd(b,a%b);//求最大公约数
}

int main()
{
	int n,m;
	int i,j;
	int count=0;
	int a[100];
	int s[10000];
	for(i=0;i<10000;i++)
		s[i]=0;//将数组初始化,假设都不可以卖得出
	s[0]=1;//
	scanf("%d",&n);
	for(i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		if(i==1)
			m=a[i];//初始化m
		else m=gcd(a[i],m);//求m与a[i]的最大公约数
		for(j=0;j<10000;j++)
		{
			if(s[j]==1)
				s[j+a[i]]=1;//用递推将各自倍数的位置与各倍数和的位置置1,表示可以卖的完
		}
	}
	if(m!=1)//如果最大公约数不为1,则有无穷种卖不出
		printf("INF\n");
	else
	{
		for(i=0;i<10000;i++)
			if(s[i]==0)
				count++;//统计卖不出的点,及为0的点
		printf("%d\n",count);
	}
	return 0;
}

1.4.6 测试数据或截图

在这里插入图片描述
图1-9 程序运行截图
在这里插入图片描述
图1-10 程序运行截图
1.5第五题
1.5.1题目内容
小明正在整理一批历史文献。这些历史文献中出现了很多日期。小明知道这些日期都在1960年1月1日至2059年12月31日。令小明头疼的是,这些日期采用的格式非常不统一,有采用年/月/日的,有采用月/日/年的,还有采用日/月/年的。更加麻烦的是,年份也都省略了前两位,使得文献上的一个日期,存在很多可能的日期与其对应。比如02/03/04,可能是2002年03月04日、2004年02月03日或2004年03月02日。给出一个文献上的日期,你能帮助小明判断有哪些可能的日期对其对应吗?
1.5.2题目要求
【输入】
一个日期,格式是"AA/BB/CC"。 (0 <= A, B, C <= 9)
【输出】
若干个不相同的日期,每个日期一行,格式是"yyyy-MM-dd"。多个日期按从早到晚排列。
1.5.3设计思想
采用循环搜索匹配,当y%100 == y, m == month,d == day时判断该年份是否闰年,然后循序输出日期,同理可得其他情况。
在这里插入图片描述
图1-11 程序流程图
1.5.4算法分析
时间复杂度为O(n^3)。
1.5.5核心代码

#include<stdio.h>
int main()
{/*循环搜索匹配法*/
	int year,month,day;
	int y,m,d;
	scanf("%d/%d/%d",&year,&month,&day);
	for(y=1960;y<=2059;y++)
	{
		for(m=1;m<=12;m++)
		{
			for(d=1;d<=31;d++)
			{
				if(y%100==year && m==month && d==day)
				{
					if(d==29 && m==2 )
					{
						if(y%4==0&&y%100!=0||y%400==0)
							printf("%d-%02d-%02d\n",y,m,d);//判断顺序时是否是平年的2月29日
					}
					else if(y%100>30&&m==2 || m==2&&d>30)
						break;
					else if((m==4||m==6||m==9||m==11)&&d>30)
						break;
					else 
						printf("%d-%02d-%02d\n",y,m,d);
				}
				 if(y%100==day && m==year && d==month && month!=day)//后一个条件排除重复输出
					printf("%d-%02d-%02d\n",y,m,d);
				 else if(y%100>30&&m==2 || m==2&&d>30)
					break;
				 else if((m==4||m==6||m==9||m==11)&&d>30)
					break;
				if(y%100==day && d==year && m==month && year!=month && year!=day )//后两个条件排除重复输出
					printf("%d-%02d-%02d\n",y,m,d);
			}
		}
	}
	return 0;
}

1.5.6 测试数据或截图

在这里插入图片描述
图1-12 程序运行截图
1.6第六题
1.6.1题目内容
G将军有一支训练有素的军队,这个军队除开G将军外,每名士兵都有一个直接上级(可能是其他士兵,也可能是G将军)。现在G将军将接受一个特别的任务,需要派遣一部分士兵(至少一个)组成一个敢死队,为了增加敢死队队员的独立性,要求如果一名士兵在敢死队中,他的直接上级不能在敢死队中。
请问,G将军有多少种派出敢死队的方法。注意,G将军也可以作为一个士兵进入敢死队。
1.6.2题目要求
【输入】
输入的第一行包含一个整数n,表示包括G将军在内的军队的人数。军队的士兵从1至n编号,G将军编号为1。接下来n-1个数,分别表示编号为2, 3, … ,n的士兵的直接上级编号,编号i的士兵的直接上级的编号小于i 。
【输出】
输出一个整数,表示派出敢死队的方案数。由于数目可能很大,你只需要输出这个数除10007的余数即可。
1.6.3设计思想
如果士兵去了,则它的直接上级不能去,通过数组初始化,1代表能去,0则不能去,通过递归找出能去的情况。
在这里插入图片描述
图1-13 程序流程图
1.6.4算法分析
时间复杂度为O(n^2)。
1.6.5核心代码

#include<stdio.h>
int per_point[1000],a[1000];
int N,count=0;
void f(int k)
{//递归
	if(k==N+1)
	{
		for(int i=1;i<=N;i++)
			if(a[i]==1)
			{
				count++;
				break;
			}	
			return ;
	}
	for(int i=0;i<=1;i++)
	{
		if(i==1 && a[per_point[k]]==1)
			return ;
		else
		{
			a[k]=i;
			f(k+1);
		}
	}
}

int main()
{
	scanf("%d",&N);//军队总人数
	per_point[1]=1;//第一个默认为G将军
	for(int i=2;i<=N;i++)
		scanf("%d",&per_point[i]);//输入每个士兵直接上级的编号
	f(1);
	printf("%d\n",count%10007);
	return 0;
}

1.6.6 测试数据或截图
在这里插入图片描述
图1-14 程序运行截图
在这里插入图片描述
图1-15 程序运行截图
1.7第七题
1.7.1题目内容
在这里插入图片描述
你一定听说过“数独”游戏。如图,玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个同色九宫内的数字均含1-9,不重复。数独的答案都是唯一的,所以,多个解也称为无解。本图的数字据说是芬兰数学家花了3个月的时间设计出来的较难的题目。但对会使用计算机编程的你来说,恐怕易如反掌了。本题的要求就是输入数独题目,程序输出数独的唯一解。我们保证所有已知数据的格式都是合法的,并且题目有唯一的解。
1.7.2题目要求
【输入】
格式要求,输入9行,每行9个数字,0代表未知,其它数字为已知。
【输出】
输出9行,每行9个数字表示数独的解。
1.7.3设计思想
将99的九宫格划分为33的九宫格,判断行和列上是否有重复的数字,将九宫格划分成不同的位置,使用深度优先遍历检索,直到最后一个位置,退出检索。
1.7.4算法分析
时间复杂度为O(n^2)。
1.7.5核心代码

#include <stdio.h>
//读取未完成的数独矩阵
void readMatrix(int array[9][9])
{
    int row; //行
    int col; //列
    for(row=0; row<9; row++)
        for(col=0; col<9; col++)
            scanf("%1d",&array[row][col]);
}
//判断在行和列上有没有重复数字(不重复true,重复false)
bool isRowAndColRepeat(int array[9][9],int row,int col,int num)//bool选择真假
{
    int i; //计数
    //判断行上有没有重复的数字 
    for(i=0; i<9; i++)
        if(array[row][i]==num)
            return false;
    //判断列上有没有重复的数字
    for(i=0; i<9; i++)
        if(array[i][col]==num)
            return false;
    return true;
}
//判断 判断位置 属于哪个3x3九宫格 
int getRowOrCol(int num)
{
    if(num<3)
        return 0;
    else if(num<6)
        return 3;
    else
        return 6;
}
//判断3x3九宫格(即同色九宫格)内是否有重复的数字(不重复true,重复false) 
bool isBlockRepeat(int array[9][9],int row,int col,int num)
{
    int getRowOrCol(int num); //得到3x3九宫格起始位置的函数原型声明 
    int rowStart, //行号
    	colStart, //列号
    	i,j; //计数
    rowStart=getRowOrCol(row);
    colStart=getRowOrCol(col);
    for(i=rowStart; i<rowStart+3; i++)
        for(j=colStart; j<colStart+3; j++)
            if(array[i][j]==num)
                return false;
    return true;
}
//对数独九宫格进行深度优先检索
void dfs(int array[9][9],int row,int col)
{
    int i,j; //计数 //递归,设置出口:如果行号row超出数组行下标,则退出递归即输出答案
    if(row>8)
    {
        printf("\n\n");
		for(i=0; i<9; i++)
        {
            for(j=0; j<9; j++)
                printf("%d",array[i][j]);
            printf("\n");
        }
        return;
    }
    //如果array[row][col] == 0即该位置的数未确定(不是题目中给好的数,可以更改)
    if(array[row][col]==0)
    {
        for(i=1; i<10; i++)
        	//从1~9开始试数 for(i = 1;i < 10;i++) //如果1~9中有合适的数,则该位置的数为该数,并开始遍历下一个位置
            if(isRowAndColRepeat(array,row,col,i) && isBlockRepeat(array,row,col,i))
            {
                array[row][col]=i;
                dfs(array,row + (col+1)/9,(col+1)%9); //当每一行的单元格都遍历过,再进入下一行 
            }
        array[row][col] = 0; //如果没有(不是)合适的数,则重置为0
    }
    else
    { // 如果该位置的数已确定则跳过该位置,遍历下一个位置
        dfs(array,row+(col+1)/9,(col+1)%9); //当每一行的单元格都遍历过,再进入下一行
    }
}
int main()
{
    int array[9][9]; 
    readMatrix(array);//读取数独题目 
    dfs(array,0,0);//从第一个单元开始深度优先检索 
    return 0;
}

1.7.6 测试数据或截图

在这里插入图片描述
图1-16 程序运行截图
在这里插入图片描述
图1-17 程序运行截图
1.8第八题
1.8.1题目内容
数轴上有n个闭区间:D1,…,Dn。其中区间Di用一对整数[ai, bi]来描述,满足ai < bi。已知这些区间的长度之和至少有10000。所以,通过适当的移动这些区间,你总可以使得他们的“并”覆盖[0, 10000]——也就是说[0, 10000]这个区间内的每一个点都落于至少一个区间内。你希望找一个移动方法,使得位移差最大的那个区间的位移量最小。具体来说,假设你将Di移动到[ai+ci,bi+ci]这个位置。你希望使得maxi{|ci|} 最小。
1.8.2题目要求
【输入】
输入的第一行包含一个整数n,表示区间的数量。
接下来有n行,每行2个整数ai, bi,以一个空格分开,表示区间[ai, bi]。
保证区间的长度之和至少是10000。
【输出】
输出一个数字,表示答案。如果答案是整数,只输出整数部分。如果答案不是整数,输出时四舍五入保留一位小数。
1.8.3设计思想
首先先把所有区间进行排序,然后求他们所有区间的最大间隔区间,往大的方向移动,同时它又是最小的移动,然后放大区间,是奇数,则保留小数。
在这里插入图片描述
图1-18 程序流程图
1.8.4算法分析
时间复杂度为O(n)。
1.8.5核心代码

#include<iostream>
#include<algorithm>//sort函数
using namespace std;
struct node
{
	int x;//左区间
	int y;//右区间
	bool operator <(const node &b)const//结构体内嵌套比较函数
	{
		return x<b.x;//如果x<b.x,那么就是从小到大排序,但是优先队列的是相反的
		
	}
}d[10010];
int max(int a,int b)//返回最大值
{
	return a>b?a:b;
}
int main()
{
	int n,i;
	cin>>n;
	for(i=0;i<n;i++)
		cin>>d[i].x>>d[i].y;//输入左右区间
	sort(d,d+n);//从小到大排序
	int maxn=0,res;
	for(i=1;i<n;i++)
	{
		res=(d[i].x - d[i-1].y);
		maxn=max(maxn,res);//求最大间隔区间
	}
	int t=0;
	if(d[0].x!=0)
		t=d[0].x;
	if(d[n-1].y<10000)
		t=max(t,(10000 - d[n-1].y));
	if(t*2<maxn)
	{
		if(maxn%2==1)
			cout<<maxn/2.0<<endl;
		else
			cout<<maxn/2<<endl;
	}
	else
		cout<<t<<endl;
	return 0;
}

1.8.6 测试数据或截图
在这里插入图片描述
图1-19 程序运行截图
在这里插入图片描述
图1-20 程序运行截图
2.第二部分(若没有做则可以不写)
2.1第一题
2.1.1题目内容
最长公共子串问题
假设有两个字符串(可能包含空格),找出其中最长的公共连续子串,并输出其长度。
2.1.2题目要求
【输入】
输入为两行字符串(可能包含空格),长度均小于等于50
【输出】
输出为一个整数,表示最长公共连续子串的长度
2.1.3设计思想
采用二维数组赋值为1代表两个字符串相等,连续位置的值为1代表字符串连续相等,最后修改最大相同字符串的长度。
在这里插入图片描述
图2-1 程序流程图
2.1.4算法分析
时间复杂度为O(n^2)。
2.1.5核心代码

#include<stdio.h>
#include<string.h>
#define N 50
int main()
{
	int i,j,k=0;
	int count=0,max_length=0;
	char str1[N],str2[N],str[N]={'0'};
	int a[N][N];
	gets(str1);
	gets(str2);
	for(i=0;i<strlen(str1);i++)
		for(j=0;j<strlen(str2);j++)
		{
			if(str1[i]==str2[j])
			{
				a[i][j]=1;
			}
			if(a[i][j]==1 && a[i++][j++]==1)
			{
				count++;
				max_length=max_length>=count?max_length:count;
			}
		}
		printf("%d\n",max_length);
		return 0;
}

2.1.6 测试数据或截图
在这里插入图片描述
图2-2 程序运行截图
2.2第二题
2.2.1题目内容
Huffman树在编码中有着广泛的应用。在这里,我们只关心Huffman树的构造过程。
给出一列数{pi}={p0, p1, …, pn-1},用这列数构造Huffman树的过程如下:
1.找到{pi}中最小的两个数,设为pa和pb,将pa和pb从{pi}中删除掉,然后将它们的和加入到{pi}中。这个过程的费用记为pa + pb。
2.重复步骤1,直到{pi}中只剩下一个数。
在上面的操作过程中,把所有的费用相加,就得到了构造Huffman树的总费用。
本题任务:对于给定的一个数列,现在请你求出用该数列构造Huffman树的总费用
例如,对于数列{pi}={5, 3, 8, 2, 9},Huffman树的构造过程如下:
(1)找到{5, 3, 8, 2, 9}中最小的两个数,分别是2和3,从{pi}中删除它们并将和5加入,得到{5, 8, 9, 5},费用为5。
(2) 找到{5, 8, 9, 5}中最小的两个数,分别是5和5,从{pi}中删除它们并将和10加入,得到{8, 9, 10},费用为10。
(3)找到{8, 9, 10}中最小的两个数,分别是8和9,从{pi}中删除它们并将和17加入,得到{10, 17},费用为17。
(4)找到{10, 17}中最小的两个数,分别是10和17,从{pi}中删除它们并将和27加入,得到{27},费用为27。
(5)现在,数列中只剩下一个数27,构造过程结束,总费用为5+10+17+27=59。
2.2.2题目要求
【输入】
输入的第一行包含一个正整数n(n<=100)。
接下来是n个正整数,表示p0, p1, …, pn-1,每个数不超过1000。
【输出】
输出用这些数构造Huffman树的总费用。
2.2.3设计思想
采用升序排序,不构建哈夫曼树,将数组中最小的两个相加作为费用,再次排序,以此类推。
在这里插入图片描述
图2-3 程序流程图
2.2.4算法分析
时间复杂度为O(n^2)。
2.2.5核心代码

#include<stdio.h>
#define N 100
void sort(int n, int *Hfm)
{
    int i,j,temp;
    for (i = 0; i < n - 1; i++)
    {
        for (j = 0; j < n - 1 - i; j++)
        {
            if (Hfm[j] < Hfm[j+1])
            {
                temp = Hfm[j];
                Hfm[j] = Hfm[j+1];
                Hfm[j+1] = temp;   
            }
        }
    }
}
int main()
{
    int n,i,sum=0;
    int Hfm[N];
   
    scanf("%d", &n);
    for (i = 0; i < n; i++)
        scanf("%d", &Hfm[i]);
   
    sort(n, Hfm);   
    for (i = n - 1; i > 0; i--)
    {
        Hfm[i-1] = Hfm[i] + Hfm[i-1];
        sum += Hfm[i-1];
        sort(i, Hfm);       
    }
    printf("%d\n", sum);
    return 0;
}

2.2.6 测试数据或截图
在这里插入图片描述
图2-4 程序运行截图
3.第二部分(ACM实践必做题)
PKU Judge Online账号:318202051219
3.1第一题(1004)
3.1.1题目内容
英文原文:
Larry graduated this year and finally has a job. He’s making a lot of money, but somehow never seems to have enough. Larry has decided that he needs to grab hold of his financial portfolio and solve his financing problems. The first step is to figure out what’s been going on with his money. Larry has his bank account statements and wants to see how much money he has. Help Larry by writing a program to take his closing balance from each of the past twelve months and calculate his average account balance.
中文翻译:
拉里今年毕业了,终于找到一份工作。他赚了很多钱,但不知怎么的,他似乎从来没有足够的钱。拉里已经决定,他需要捉住他的金融投资组合,并解决他的融资问题。第一步是弄清他的钱是怎么回事。拉里有他的银行账户结单,想看看他有多少钱。帮助拉里编写一个程序,从过去的12个月里每个月取得他的期末余额,并计算他的平均账户余额。
3.1.2题目要求
【输入】
输入为12行。每行将包含他的银行账户特定月份的期末余额。每个数字将是正数。
【输出】
输出将是一个数字,即12个月期终余额的平均数。
3.1.3设计思想
直接for循环输入求平均值。
在这里插入图片描述
图3-1 程序流程图
3.1.4算法分析
时间复杂度为O(1)。
3.1.5核心代码

#include<stdio.h>
int main()
{
	double sum=0.0,money;
	int i;
	for(i=1;i<=12;i++)
	{
		scanf("%lf",&money);
		sum+=money;
	}
	printf("$%.2lf\n",sum/12);
	return 0;
}

3.1.6 测试数据或截图
在这里插入图片描述
图3-2 poj通过截图
在这里插入图片描述
图3-3 程序运行截图

3.2第二题(1012)
3.2.1题目内容
英文原文:
The Joseph’s problem is notoriously known. For those who are not familiar with the original problem: from among n people, numbered 1, 2, . . ., n, standing in circle every mth is going to be executed and only the life of the last remaining person will be saved. Joseph was smart enough to choose the position of the last remaining person, thus saving his life to give us the message about the incident. For example when n = 6 and m = 5 then the people will be executed in the order 5, 4, 6, 2, 3 and 1 will be saved.
Suppose that there are k good guys and k bad guys. In the circle the first k are good guys and the last k bad guys. You have to determine such minimal m that all the bad guys will be executed before the first good guy.
中文翻译:
约瑟夫的问题众所周知。对于那些不熟悉原始问题的人:从n个人中,编号1,2,3…每个人都要被处决,只有最后一个人的生命才会被拯救。约瑟夫很聪明,选择了最后一个留下来人的位置,从而挽救了他的生命,向我们传达了这一事件的信息。例如,当n=6和m=5时,人们将按5、4、6、2、3、和1的顺序被处死。
假如有好坏人,在这个圈子里,前k个是好人,最后k个是坏人。你必须群定最小的m,在第一个好人之前,所有的坏人都会被处决。
3.2.2题目要求
【输入】
输入文件有包含k的单独行组成,输入文件中的最后一行包含0。你可以假设0<k<14。
【输出】
输出文件将由包含与输入文件中对应的m的单独行组成。
3.2.3设计思想
当前k个好人有被杀掉时,m++,直到前k个都没有被杀而后k个已经被杀完,则输出结果。
在这里插入图片描述
图3-4 程序流程图
3.2.4算法分析
时间复杂度为O(n^2)。
3.2.5核心代码

#include <stdio.h> 
int main()
{
    int n;
	int k;
	int m;
    int a[14];//最多有13个好人
    for (n=1; n<=13; n++)
    {
	for (k=n+1; ; k++)
	{
	    int num=2*n;//总人数
	    int wei=0;
	    int judge=0;
	    int s=0;
	    while (1)
	    {
		wei=(k+wei)%num;
		if (wei<=n && wei>=1)
		    break;
		if (wei==0)
		    wei+=num;
		wei--;
		num--;
		if (num==n)
		{
		    judge=1;
		    break;
		}	
	    }
	    if (judge)
			break;
	} 
	a[n]=k;
    }
    while(scanf("%d", &m)!=EOF && m)
    {
		printf("%d\n", a[m]);
    }
    return 0;
}

3.2.6 测试数据或截图
在这里插入图片描述
图3-5 poj通过截图
在这里插入图片描述
图3-6 程序运行截图
3.3第三题(1013)
3.3.1题目内容
英文原文:
Sally Jones has a dozen Voyageur silver dollars. However, only eleven of the coins are true silver dollars; one coin is counterfeit even though its color and size make it indistinguishable from the real silver dollars. The counterfeit coin has a different weight from the other coins but Sally does not know if it is heavier or lighter than the real coins.
Happily, Sally has a friend who loans her a very accurate balance scale. The friend will permit Sally three weighings to find the counterfeit coin. For instance, if Sally weighs two coins against each other and the scales balance then she knows these two coins are true. Now if Sally weighs
one of the true coins against a third coin and the scales do not balance then Sally knows the third coin is counterfeit and she can tell whether it is light or heavy depending on whether the balance on which it is placed goes up or down, respectively.
By choosing her weighings carefully, Sally is able to ensure that she will find the counterfeit coin with exactly three weighings.
中文翻译:
莎莉琼斯有一打旅行者银币,然而,只有十一枚硬币是真正的银元;一枚硬币是假的,尽管它的颜色和大小使它与真正的银元无法区分。假币的重量与其他硬币不同,但莎莉不知道它比真正的银币重还是轻。
令人高兴的是,莎莉有一个朋友,他借给她一个非常精确的天平。这位朋友将允许莎莉三次称重以找到那枚假硬币。例如,如果莎莉称两枚硬币相对,天平平衡,那么她就知道这两枚硬币是真的。如果莎莉称一个真正的硬币和第三个硬币的天平不平衡,那么莎莉知道第三个硬币是假的,她可以分辨它是轻的还是重的,这取决于它放置的天平分别是上升还是下降。
通过仔细选择她的称量,莎莉能够确保她能够找到有三次称量的假币。
3.3.2题目要求
【输入】
输入的第一行是整数n(n>0),指定后面对的情况数。每个箱子由三条输入线组成,每个称重一条。莎莉用字母A-L称重的信息将由两串字母提供,然后是“向上”、“向下”或“平衡”。第一串字母代表左边的硬币,第二串,硬币在正确地平衡。(莎莉总是把硬币的数量放在右边的余额上,就像放在左边的余额上一样。)第三个未知的单词将显示平衡的右侧是上升还是下降,还是保持平衡。
【输出】
对于每一种情况,输出将通过他的字母来识别假币,并判断它是重的还是轻的。觉接方案将永远是独一无二的。
3.3.3设计思想
因为有十二枚硬币,但只能称两次,故每次称四枚,然后变换其中两枚,比较它们是轻了还是重了,就可以得出那枚假币。

在这里插入图片描述
图3-7 程序流程图
3.3.4算法分析
时间复杂度为O(n)。
3.3.5核心代码

#include <iostream>
#include <cstring>
using namespace std;
char Left[3][7];
char Right[3][7];
char result[3][7];

bool IsFake(char c, bool light);
int main()
{
    int t;
    cin >> t;
    while(t--)
    {
        for(int i=0; i<3; i++)
        {
            cin >> Left[i] >> Right[i] >> result[i];
        }
        for(char c='A'; c<='L'; c++)

        {
            if(IsFake(c, true))
            {
                cout << c  <<" is the counterfeit coin and it is light.\n";
                break;
            }
            else if(IsFake(c, false))
            {
                cout << c  <<" is the counterfeit coin and it is heavy.\n";
                break;
            }
        }
    }
    return 0;
}

bool IsFake(char c, bool light)
{
    for(int i=0; i<3; i++)
    {
        char *pLeft;
        char *pRight;
 
        if(light)
        {
            pLeft = Left[i];
            pRight = Right[i];
        }
        else
        {
            pLeft = Right[i];
            pRight = Left[i];
        }
        switch(result[i][0])
        {
            case 'u':
                if( strchr(pRight, c) == NULL)
                    return false;
                break;
            case 'e':
                if( strchr(pRight, c) || strchr(pLeft, c))
                    return false;
                break;
            case 'd':
                if( strchr(pLeft, c) == NULL)
                    return false;
                break;
        }
    }
    return true;
}

3.3.6 测试数据或截图
在这里插入图片描述
图3-8 poj通过截图
在这里插入图片描述
图3-9 程序运行截图

3.4第四题(1014)
3.4.1题目内容
英文原文:
Marsha and Bill own a collection of marbles. They want to split the collection among themselves so that both receive an equal share of the marbles. This would be easy if all the marbles had the same value, because then they could just split the collection in half. But unfortunately, some of the marbles are larger, or more beautiful than others. So, Marsha and Bill start by assigning a value, a natural number between one and six, to each marble. Now they want to divide the marbles so that each of them gets the same total value. Unfortunately, they realize that it might be impossible to divide the marbles in this way (even if the total value of all marbles is even). For example, if there are one marble of value 1, one of value 3 and two of value 4, then they cannot be split into sets of equal value. So, they ask you to write a program that checks whether there is a fair partition of the marbles.
中文翻译:
玛莎和比尔拥有一套弹珠。他们想把收藏品分给自己,这样两个人都能得到同等份额的弹珠。如果所有的弹珠都有相同的价值,这是很容易的,因为这样他们就可以把收藏品分成两份。但不幸的是,有些弹珠更大,或者更漂亮。因此,玛莎和比尔首先给每个弹珠赋予一个值,一个介于1到6之间的自然数。现在他们想把弹珠分成两半,这样每个弹珠的总价值都是一样的。不幸的是,他们意识到用这种方式分割弹珠可能是不可能的(即使所有弹珠的总价值都是偶数)。例如,如果有价值1的弹珠、价值3的弹珠和价值4的弹珠,那么它们就不能被分割成等价物。因此,他们要求你写一个程序,检查是否有一个公平的分割弹珠。
3.4.2题目要求
【输入】
输入文件的每一行描述一个要分割的弹珠集合。这些行包含六个非负整数n1,……,n6,其中ni是值i的弹珠数。因此,上面的例子将用输入行“1 0 1 2 0”来描述。最大的弹珠总数将是20000枚。输入文件的最后一行将是“0 0 0”;不要处理这一行。
【输出】
对于每个集合,输出“Collenction#k ”,其中k是测试用例的数目,然后“可以被分割”。或者“不能被分割”。在每个测试用例之后输出一个空行。
3.4.3设计思想
如果弹珠的总价值是奇数,则不能被分割,为偶数时,如果位置1、3、4有弹珠时也不能分割。
在这里插入图片描述
图3-10 程序流程图
3.4.4算法分析
时间复杂度为O(n)。
3.4.5核心代码

#include<iostream>
using namespace std;

int n[7];  //价值为i的物品的个数
int SumValue;  //物品总价值
int HalfValue;  //物品平分价值
bool flag;    //标记是否能平分SumValue
 
void DFS(int value,int pre)
{
    if(flag)
        return;
    if(value==HalfValue)
    {
        flag=true;
        return;
    }
    for(int i=pre;i>=1;i--)
    {
        if(n[i])
        {
            if(value+i<=HalfValue)
            {
                n[i]--;
                DFS(value+i,i);
                if(flag)
                  break;
            }
       }
    }
    return;
}

int main()
{
    int i;
	int test=1;
    while(cin>>n[1]>>n[2]>>n[3]>>n[4]>>n[5]>>n[6])
    {
        SumValue=0;  //物品总价值
        for(i=1;i<=6;i++)
            SumValue+=i*n[i];
        if(SumValue==0)
            break;
        if(SumValue%2==1)    //sum为奇数,无法平分
        {
            cout<<"Collection #"<<test++<<':'<<endl;
            cout<<"Can't be divided."<<endl<<endl;    //注意有空行
            continue;
        }
        HalfValue=SumValue/2;
        flag=false;
        DFS(0,6);
        if(flag)
        {
            cout<<"Collection #"<<test++<<':'<<endl;
            cout<<"Can be divided."<<endl<<endl;
            continue;
        }
        else
        {
            cout<<"Collection #"<<test++<<':'<<endl;
            cout<<"Can't be divided."<<endl<<endl;
            continue;
        }
    }
    return 0;
}

3.4.6 测试数据或截图
在这里插入图片描述
图3-11 poj通过截图

在这里插入图片描述
图3-12 程序运行截图
3.5第五题
3.5.1题目内容
英文原文:
There is given the series of n closed intervals [ai; bi], where i=1,2,…,n. The sum of those intervals may be represented as a sum of closed pairwise non−intersecting intervals. The task is to find such representation with the minimal number of intervals. The intervals of this representation should be written in the output file in acceding order. We say that the intervals [a; b] and [c; d] are in ascending order if, and only if a <= b < c <= d.
Task
Write a program which:
reads from the std input the description of the series of intervals,
computes pairwise non−intersecting intervals satisfying the conditions given above,
writes the computed intervals in ascending order into std output.
中文翻译:
给出了n个区间[ai,bi]的级数,其中i=1,2,……,n,这些区间和和可以表示为闭成对的非-相交区间的和。任务是用最少的间隔找到这种表示此表示的间隔应按加入的顺序写入输出文件中。我们说区间[a,b]和[c,d]是升序的当且仅当a<=b<c<=d。
任务
编写一个程序,其中:
从STD输入读取一系列将的描述,计算满足上述条件的成对非-相交区间,将计算出的间隔按升序写入std输出。
3.5.2题目要求
【输入】
在输入的第一行中有一个整数n,3<=n<=50000。这是间隔的数目。在(i+1)-st行,1<=i<=n中,对区间[ai,bi]的描述是以两个整数ai和bi为形式,有单个空格分隔,分别是区间的开始和结束,1<=ai<bi<=1000000。
【输出】
输出应该包含所有计算成对的非-相交间隔的描述。在每一行中应该写一段时间间隔的描述。它应该有两个整数组成,分别用一个扣扣给你个分隔,间隔的开始和结束。间隔应按升序写入输出
3.5.3设计思想
首先对所有区间进行排序,如果上一个的右区间小于下一个的左区间,则输出上一个的区间,继续比较下一个区间,如果区间有重叠,则合并区间。
在这里插入图片描述
图3-13 程序流程图
3.5.4算法分析
时间复杂度为O(n)。
3.5.5核心代码

#include<stdio.h>
#include<algorithm>
using namespace std; 
#define N 50005 
struct node 
{  
	int c;//左区间
	int d; //右区间
}f[N]; 

bool cmp(node x,node y)
{
	if((x.c<y.c)||(x.c==y.c)&&(x.d<y.d))
		return true;
	return false;
}

int main() 
{     
	int n,i;
	int a,b;
	scanf("%d",&n);
	for(i=0;i<n;i++)    
		scanf("%d%d",&f[i].c,&f[i].d);//输入左右区间 
	sort(f,f+n,cmp);
	a=f[0].c;
	b=f[0].d;   
	for(i=1;i<n;i++)   
	{    
		if(f[i].c>b)                //若区间不交叉,输出上一个区间    
		{     
			printf("%d %d\n",a,b);               
			a=f[i].c;     
			b=f[i].d;    
		}    
		else if(b<f[i].d)      
			b=f[i].d;        //否则,判断当前右端是否大于上一区间的右端   
	}   
	printf("%d %d\n",a,b);  
return 0; 
}

3.5.6 测试数据或截图
在这里插入图片描述
图3-14 poj通过截图
在这里插入图片描述
图3-15 程序运行截图
4.心得体会
为期三周的acm实习在快乐掉头发中不知不觉就结束了,这段时间来可谓困难重重,也收获丰多。作为计算机学生而言,我一直认为能多学一点是一点,但一开始并不清楚acm是要干嘛的,所以一开始的新鲜感使然,编程序时也尤为快乐,思路清晰,前面部分的题目也游刃有余,但随着时间的过去,焦躁感也慢慢升起,题目的难度也在不断变难,坐一个早上仍敲不出一题也屡见不鲜,越是烦躁,就越是没有思路,更坐不定来分析题目,到后面不断调节情绪,才开始有点起色。我们的指导老师一开始就给我们灌输学会调试程序很重要,虽然对于我这个菜鸟来说编程更尤为困难,但还是下意识的去慢慢调试程序,在调试过程中不断把程序完善,最后看着自己的程序能成功的运行,心中满是愉悦。这次acm实习对于我而言真的收获多多:
一、思想上
(1)万事开头难,凡事都要坚持
ACM对逻辑思维要求能力很强,当在做题过程中遇到难题时一定不要轻言放弃,要有一夫当关万夫莫开的勇气,不作出不罢休的决心。
(2)对自己负责,对自己要有信心
不以物喜,不以己悲,不因一时做出而得意忘形,也不因一时做不出而妄自菲薄。做出时要沉住性子回来仔细检查条件,做不出时也要仔细找关键突破点。
二、行为上
有目的有针对性的训练
通过三周的acm实习,知道哪些算法和知识点是自己的薄弱点,在今后的学习和做题中也有针对性地找这类题目进行训练,在不断学习中完善形成系统的知识体系。
时光总是易逝的,学习也总是五味杂陈,而此次的实习令我收获很大,同时也希望学校有条件的话多组织这类实习。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值