T1:问题 A: 亲密数对
题目描述
给定两个不同的正整数a和b,如果a的因子和等于b,b的因子和等于a,且a ≠ b,则a和b为一对亲密数。给定正整数N,求2~N中的亲密数对。
输入
第1行一个正整数N,1≤N≤2000。
输出
输出若干行,每行有两个用一个空格隔开的正整数,表示一对亲密数。
样例输入
200
样例输出
48 75
75 48
140 195
195 140
题解
这道题数据规模不大,完全可以先枚举出来再打表,当然直接枚举也能过(一共不超过10个.....)。注意是a的因子和等于 b ,而不是b的因子,考试时要格外注意!!
参考代码
#include<cstdio>
using namespace std;
int n,p[3000];
int main()
{
scanf("%d",&n);
for(int i=2;i<=n;i++)
{
for(int j=2;j<=i;j++)
{
if(i%j==0&&i!=j) p[i]+=j;
}
}
for(int i=2;i<=n;i++)
{
for(int j=2;j<=n;j++)
{
if(i!=j)
{
if(p[i]==j&&p[j]==i)
{
printf("%d %d\n",i,j);
}
}
}
}
return 0;
}
T2:问题 B: 吉祥数
题目描述
为了迎接春节,信息学兴趣小组的同学们在辅导老师的领导下,举办了一个盛大的晚会,晚会的第一项内容是做游戏:猜数。老师给每个同学发一张卡片,每张卡片上都有一个编号(此编号为非负数,且小于255),每个编号互不相同。老师制定了以下的游戏规则:第一轮,每个同学将自己编号的各位数字进行二次方后再相加得到一组数,编号在这组数中出现的同学淘汰出局;第二轮,余下的同学再将自己编号的各位数字进行三次方后相加得到一组新数,编号在这组数中出现的同学再淘汰出局;第三轮,余下的同学再将编号的各位数字进行4次方相加得到一组新数,编号在这组数中出现的同学再淘汰出局,依此类推,经过n轮后,仍留下来的同学将获得春节特别礼物,卡片上的数即2018年吉祥数。
假设班级人数不超过200人。
输入
第1行为1个正整数n(n<8),表示有n轮游戏。
第2行是卡片上互不相同的编号,以一个空格隔开。
输出
一行数,表示剩下来的各个吉祥数,按从小到大的顺序输出,每两个数之间有一个空格。
样例输入
1
24 123 2 12 20 14 4 6 36 72
样例输出
2 6 12 24 72 123
题解
第一眼看到这道题,就感觉这是一道模拟题。首先遇到了次方,先看看是否用高精,却发现n挺小,就不用再写高精。这道题的模拟方式就是枚举轮数,对于每一轮,判断剩下的数中次方后是否与原数列有相同的,若有,就删去,否则就不变。如此n轮后,升序排序,输出结果即可。
参考代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int len,n,a[330],vis[330],cnt=0,t=0,p[330],x[330];
int exp1(int x1,int k1)
{
int ret=1;
for(int i=1;i<=x1;i++) ret*=k1;
return ret;
}
int main()
{
scanf("%d\n",&n);
while(scanf("%d",&a[cnt+1])!=EOF) cnt++;
for(int i=1;i<=n;i++)
{
t=0;
memset(p,0,sizeof(p));
for(int j=1;j<=cnt;j++)
{
if(vis[j]) continue;
int i0=a[j];t++;
while(i0)
{
p[t]+=exp1(i+1,i0%10);
i0/=10;
}
}
for(int j=1;j<=t;j++)
{
for(int k=1;k<=cnt;k++)
{
if(!vis[k])
{
if(p[j]==a[k])
{
vis[k]=1;
}
}
}
}
}
t=0;
for(int i=1;i<=cnt;i++)
if(!vis[i]) x[++t]=a[i];
sort(x+1,x+t+1);
for(int i=1;i<=t;i++) printf("%d ",x[i]);
return 0;
}
T3:问题 C: 二叉树输出
题目描述
树的凹入表示法主要用于树的屏幕或打印输出,其表示的基本思想是兄弟间等长,一个结点的长度要不小于其子结点的长度。二叉树也可以这样表示,假设叶结点的长度为 1,一个非叶结点的长度等于它的左右子树的长度之和。
一棵二叉树的一个结点用一个字母表示(无重复),输出时从根结点开始:
每行输出若干个结点字符(相同字符的个数等于该结点长度),
如果该结点有左子树就递归输出左子树;
如果该结点有右子树就递归输出右子树。
假定一棵二叉树一个结点用一个字符描述,现在给出先序和中序遍历的字符串,用树的凹入表示法输出该二叉树。
输入
共两行,每行是由字母组成的字符串(一行的每个字符都是唯一的),分别表示二叉树的先序遍历和中序遍历的序列。
输出
行数等于该树的结点数,每行的字母相同。
样例输入
abdec
dbeac
样例输出
aaa
bb
d
e
c
题解
又是一道树。这道题的长度定义虽然与深度不同,但是从叶子往上扫还是能轻易得到答案。因此这道题又是通过先序、中序求树的问题。方法还是同之前一样,通过dfs函数确定对于每一个根节点的左儿子、右儿子、左子树、右子树。当l=r时就return。建立好树后,把叶子结点长度定为1,然后再来个search就OK了。
参考代码
#include<cstdio>
#include<cstring>
using namespace std;
struct tree
{
int l,r;
}tr[10000];
char p[10000],q[10000];
int len,seg[10000],rev[10000],dep[10000];
void make_tree(int l1,int r1,int l2,int r2)
{
if(l1==r1||tr[l1].l||tr[l1].r) return;
int pd1=0,pd2=0,list1=-1,list2=-1;
for(int i=l1+1;i<=r1;i++)
{
if(rev[p[i]]<rev[p[l1]]&&!pd1)
{
pd1=1;tr[l1].l=i;
list1=i;
}
if(rev[p[i]]>rev[p[l1]]&&!pd2)
{
pd2=1;tr[l1].r=i;
list2=i;
}
}
if(list1!=-1)
{
if(list2==-1) list2=r1+1;
make_tree(list1,list2-1,l2,rev[p[l1]]-1);
}
if(list2!=-1)
{
make_tree(list2,r1,rev[p[l1]]+1,r2);
}
}
void dfs(int k)
{
if(tr[k].l==0&&tr[k].r==0)
{
dep[k]=1;
return;
}
if(tr[k].l) dfs(tr[k].l);
if(tr[k].r) dfs(tr[k].r);
dep[k]=dep[tr[k].l]+dep[tr[k].r];
}
void dfs2(int k)
{
for(int i=1;i<=dep[k];i++) printf("%c",p[k]);
printf("\n");
if(tr[k].l) dfs2(tr[k].l);
if(tr[k].r) dfs2(tr[k].r);
}
int main()
{
scanf("%s",p);
scanf("%s",q);
len=strlen(p);
for(int i=0;i<len;i++) tr[i].l=tr[i].r=0;
for(int i=0;i<len;i++) seg[p[i]]=i;
for(int i=0;i<len;i++) rev[q[i]]=i;
make_tree(0,len-1,0,len-1);
dfs(0);dfs2(0);
return 0;
}
T4:问题 D: 关系网络
题目描述
有n个人,他们的编号为1~n,其中有一些人相互认识,现在x想要认识y,可以通过他所认识的人来认识更多的人(如果a认识b,b认识c,那么a可以通过b来认识c),求出x最少需要通过多少人才能认识y。
输入
第1行3个整数n、x、y,2≤n≤100;
接下来的n行是-个nxn的邻接矩阵,a[i][j]=1表示i认识j,a[i][j]=0表示不认识。保证i=j时,a[i][j]=0,并且a[i][j]=a[j][i]。
输出
一行一个整数,表示x认识y最少需要通过的人数。数据保证x一定能认识y。
样例输入
5 1 5
0 1 0 0 0
1 0 1 1 0
0 1 0 1 0
0 1 1 0 1
0 0 0 1 0
样例输出
2
题解
难道这不是一道典型的最短路吗?
问题分析中竟然介绍了一下“宽搜”,看来确实没什么需要注意的。
参考代码
#include<cstdio>
using namespace std;
int n,x,y,e_map[101][101];
int main()
{
scanf("%d%d%d",&n,&x,&y);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
scanf("%d",&e_map[i][j]);
if(e_map[i][j]==0) e_map[i][j]=707406378;
}
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(e_map[i][k]+e_map[k][j]<e_map[i][j])
e_map[i][j]=e_map[i][k]+e_map[k][j];
printf("%d",e_map[x][y]-1);
return 0;
}
T5:问题 E: Fibonacci 前 n 项和
题目描述
大家都知道 Fibonacci 数列吧,f1 =1,f2 =1,f3 =2,f4 =3,…,fn =fn−1 +fn−2 。
现在问题很简单,输入 n 和 m,求 {fn } 的前 n 项和 Sn mod m。
输入
输入 n,m。
输出
输出前 n 项和 Sn mod m。
样例输入
5 1000
样例输出
12
提示
【数据范围与提示】
对于 100% 的数据, 1≤n≤2×109 ,1≤m≤109 +10。
题解
其实我为此单独写了一个,是“三维求斐波那契”。
这题可有得说了。首先推出公式快速幂、逆元就可以得到答案,其次又容易发现sum[i+2]=f[i]-1,因此求出fabonacci就可以了。关于求fabonacci的方法,网上才真的是天花乱坠,各种神奇的方法都有。这里介绍一下debug出来的一种神奇方法。
众所周知,当我们用矩阵快速幂时,单位矩阵都是对角线上全是1,其余全是0,就达到了ret=1的初始化效果。但其实我们也知道,ret有时候是直接赋值为2、3、7等等,因此初始矩阵也不一定要是单位矩阵。例如这种方法:
初始矩阵a
1 0 0
0 1 0
1 0 1
可见这个矩阵和初始矩阵还是有区别的。
而我们的转移矩阵为:
1 1 0
1 0 0
0 0 0
其实也可以发现,与二维求feibonacci是很相似的,但是区别也是非常明显。
最后用(1,1, 1)乘以初始矩阵就可以得到(p,q,r),而我们的ans就是p。
这种方法没有什么技术含量,其实就是在二维的基础上又加了一个元素,而且根本用不到(但也有用),其他的三维构造方式还有(f[n],f[n-1],s[n])等等,但是初始矩阵不是单位矩阵的构造还是少见。
参考代码
#include<cstdio>
#define LL long long
using namespace std;
LL a[5][5],b[5][5],c[5][5],n,m;
LL qmul(LL x,LL y)//快速乘
{
LL ret=0;
while(y)
{
if(y&1)
ret=(ret+x)%m;
x=(x+x)%m;
y/=2;
}
return ret;
}
LL qpow(LL y)//矩阵快速幂
{
while(y)
{
if(y&1)
{
for(int i=1;i<=3;i++)
for(int j=1;j<=3;j++)
for(int k=1;k<=3;k++)
c[i][j]=(c[i][j]+qmul(a[i][k],b[k][j]))%m;
for(int i=1;i<=3;i++)
for(int j=1;j<=3;j++)
{
a[i][j]=c[i][j];
c[i][j]=0;
}
}
for(int i=1;i<=3;i++)
for(int j=1;j<=3;j++)
for(int k=1;k<=3;k++)
c[i][j]=(c[i][j]+qmul(b[i][k],b[k][j]))%m;
for(int i=1;i<=3;i++)
for(int j=1;j<=3;j++)
{
b[i][j]=c[i][j];
c[i][j]=0;
}
y/=2ll;
}
}
int main()
{
scanf("%lld%lld",&n,&m);
if(n==1)
{
printf("1");
return 0;
}
if(n==2)
{
printf("2");
return 0;
}
a[1][1]=a[2][2]=a[3][3]=a[3][1]=1ll;//初始化初始矩阵
b[1][1]=b[1][2]=b[2][1]=1ll;//初始化转移矩阵
qpow(n-1);
printf("%lld",(a[1][1]+a[2][1]+a[3][1]-1)%m);
return 0;
}
T6:问题 F: 数三角形
题目描述
给定一个 n×m 的网格,请计算三点都在格点上的三角形共有多少个。下图为 4×4 的网格上的一个三角形。
注意:三角形的三点不能共线。
输入
输入一行,包含两个空格分隔的正整数 m 和 n。
输出
输出一个正整数,为所求三角形数量。
样例输入
2 2
样例输出
76
提示
【数据范围与提示】
对于所有数据,1≤m,n≤1000。
题解
这道题的题意是让我们找出格子图中所有的三角形。那么是不是可以视为在所有的点中选出3个,来构成三角形呢?当然,题目中好心的提示我们,三点共线不满足条件,那把这种情况减去就是我们的答案了。因此本题转化为求三点共线的数量。先考虑最简单的情况:三个点在同一行和同一列的,就直接排列组合解决。现在我们来看斜着的,为了不重不漏,我们可以这样来想:确定2个点,就确定了一条线段。如果这条线段上还有其他点,那么可以与这两个点构成三点共线。注意,是线段,而不是直线,这样就保证了我们在枚举每一组点对时,所得的答案都不会与其他的答案冲突,此处如果还有不解,请反复看前后2句的内容,并思考线段与直线的区别。现在我们来看,这条线段上除了端点,还有多少个点。先来看所有的点吧,点的个数其实与横竖的长度有关。让我们来想一想,当横竖长度互质时,是不是只有2个点,如果每多扩展一次,点数就会增加,可以参考一条射线什么情况穿过1个点。也就是说,一共有gcd(x,y)+1个点。所以,除了端点还有gcd(x,y)-1个点,每个点都可以作为答案。现在我们再来看一共有多少个点对。点对的数量取决于横竖的长度,以及起点。我们可以选择枚举起点,即枚举横坐标i和纵坐标j,然后可知点对个数就是(m-i+1)*(n-j+1),于是我们的三点共线就考虑完毕了,ans也就很容易求解。
参考代码
#include<cstdio>
#define LL long long
using namespace std;
LL n,m,ans=0;
LL gcd(LL p,LL q) { return q==0?p:gcd(q,p%q); }
int main()
{
scanf("%lld%lld",&n,&m);
for(LL i=1;i<=n;i++)
{
for(LL j=1;j<=m;j++)
{
ans+=2ll*(n-i+1ll)*(m-j+1ll)*(gcd(i,j)-1ll);
}
}
ans+=(m+1ll)*((n+1ll)*(n-1ll)*n/6ll)+(n+1ll)*(m*(m-1ll)*(m+1ll)/6ll);
printf("%lld",(m+1ll)*(n+1ll)*((m+1ll)*(n+1ll)-1ll)*((m+1ll)*(n+1ll)-2ll)/6ll-ans);
return 0;
}
T7太难,此处略过,日后重开一篇,弥补今日之遗憾。
T7:问题 G: 加工生产调度
题目描述
某工厂收到了 n 个产品的订单,这 n 个产品分别在 A、B 两个车间加工,并且必须先在 A 车间加工后才可以到 B 车间加工。
某个产品 ii 在 A,B 两车间加工的时间分别为Ai,BiAi,Bi。怎样安排这 nn 个产品的加工顺序,才能使总的加工时间最短。
这里所说的加工时间是指:从开始加工第一个产品到最后所有的产品都已在 A,B 两车间加工完毕的时间。
输入
第一行仅—个数据 n ,表示产品的数量;
接下来 n 个数据是表示这 n 个产品在 A 车间加工各自所要的时间;
最后的 n 个数据是表示这 n 个产品在 B 车间加工各自所要的时间。
输出
第一行一个数据,表示最少的加工时间;
第二行是一种最小加工时间的加工顺序。
样例输入
5
3 5 8 7 10
6 2 1 4 9
样例输出
34
1 5 4 2 3
提示
【数据规模】
对于100%的数据, 0 < n < 10000,所有数值皆为整数。
T8:问题 H: 灯泡
题目描述
相比 wildleopard 的家,他的弟弟 mildleopard 比较穷。他的房子是狭窄的而且在他的房间里面仅有一个灯泡。每天晚上,他徘徊在自己狭小的房子里,思考如何赚更多的钱。有一天,他发现他的影子的长度随着他在灯泡和墙壁之间走到时发生着变化。一个突然的想法出现在脑海里,他想知道他的影子的最大长度。
输入
第一行包含一个整数 T ,表示测试数据的组数。
对于每组测试数据,仅一行,包含三个实数 H,h 和 D,H 表示灯泡的高度,h 表示 mildleopard 的身高,D 表示灯泡和墙的水平距离。
输出
共 T 行,每组数据占一行表示影子的最大长度,保留三位小数。
样例输入
3
2 1 0.5
2 0.5 3
4 3 4
样例输出
1.000
0.750
4.000
提示
【数据范围】
T≤100,10−2≤H,h,D≤103,10-2≤H−h。
题解
这是一道数学题,需要用到求函数最值的知识。假设人距离灯泡x,那么很容易通过相似的知识算出l关于x的表达式如下:
如此我们发现,一段是正比例函数,可以直接求出最值,一段是对勾函数,需要判断能不能取到最值。因此我们的最值表达式也就出来了:
由此可见,这道题主要是数学思想,在代码层面就很简单了。
参考代码
#include<cstdio>
#include<cmath>
using namespace std;
int main()
{
//freopen("1.out","w",stdout);
int t;
scanf("%d",&t);
while(t--)
{
double H,h,D,max1=-999999;
scanf("%lf%lf%lf",&H,&h,&D);
if(sqrt(D*(H-h))<=D*(H-h)/H)
max1=D*h/H;
if(D*(H-h)/H<=sqrt(D*(H-h))&&sqrt(D*(H-h))<=D)
if(D+H-2.0*(sqrt(D*(H-h)))>max1)
max1=D+H-2.0*(sqrt(D*(H-h)));
if(sqrt(D*(H-h))>=D)
if(h>max1)
max1=h;
printf("%.3lf\n",max1);
}
return 0;
}