C语言程序设计100例之(70):国王游戏
例70 国王游戏
问题描述
恰逢H国国庆,国王邀请n位大臣来玩一个有奖游戏。首先,他让每个大臣在左、右手上面分别写下一个整数,国王自己也在左、右手上各写一个整数。然后,让这n位大臣排成一排,国王站在队伍的最前面。排好队后,所有的大臣都会获得国王奖赏的若干金币,每位大臣获得的金币数分别是:排在该大臣前面的所有人的左手上的数的乘积除以他自己右手上的数,然后向下取整得到的结果。
国王不希望某一个大臣获得特别多的奖赏,所以他想请你帮他重新安排一下队伍的顺序,使得获得奖赏最多的大臣,所获奖赏尽可能的少。注意,国王的位置始终在队伍的最前面。
输入
第一行包含一个整数n,表示大臣的人数。
第二行包含两个整数a和b,之间用一个空格隔开,分别表示国王左手和右手上的整数。
接下来n行,每行包含两个整数a和b,之间用一个空格隔开,分别表示每个大臣左手和右手上的整数。(1≤n≤1,000,0 < a,b < 10000)
输出
一个整数,表示重新排列后的队伍中获奖赏最多的大臣所获得的金币数。
输入样例
3
1 1
2 3
7 4
4 6
输出样例
2
(1)编程思路。
假设前面已经排了几个人(包括国王),设他们左手上的数的乘积为P。
现在要给2个人排序,设第一个人左手上的数为a1,右手上的数为b1;第二个人左手上的数为a2,右手上的数为b2。
如果第一个人排在前面优于第二个人排在前面,那么有
max(S/b1,S*a1/b2) < max(S/b2,S*a2/b1)
又由于a1,b1,a2,b2,S>0,所以,S*a1/b2≥S/b2。
如果S*a1/b2 ≥ S*a2/b1,则max(S/b1,S*a1/b2)≥max(S/b2,S*a2/b1) ,与假设矛盾。
所以S*a1/b2 < S*a2/b1 ,即a1*b1 < a2*b2。
因此,需要将数组按照大臣左右手数字的乘积a*b从小到大排序。
由于左手上的数的乘积P超出了长整数的表数范围,所以采用高精度计算。
(2)源程序1。
#include <stdio.h>
#include <string.h>
struct Node
{
int a,b;
};
void mul(int *a,int b,int *c) // c=a*b
{
int len=a[0];
int i;
for (i=0;i<len+20;i++)
c[i]=0;
for (i=1; i<=len; i++)
{
c[i]+=(a[i]*b);
c[i+1]+=(c[i]/10);
c[i]%=10;
}
while (c[len+1]>0)
{
len++;
c[len+1]+=(c[len]/10);
c[len]%=10;
}
c[0]=len;
}
void div(int *a,int b,int *c) // c=a/b
{
int len=a[0];
int r=0; // 余数
int i;
for (i=len; i>=1; i--)
{
r=r*10+a[i];
c[i]=r/b;
r=r%b;
}
while (!c[len]) len--;
if (!len) len=1;
c[0]=len;
}
void getMax(int *x,int *y) // x=max(x,y)
{
if (y[0]<x[0]) return;
int i;
if (x[0]==y[0])
{
for (i=y[0]; i>=1; i--)
if (y[i]<x[i]) return;
else if (y[i]>x[i]) break;
}
for (i=0; i<=y[0]; i++)
x[i]=y[i];
}
int main()
{
int n;
scanf("%d",&n);
struct Node m[1005];
int i,j;
for (i=0; i<=n; i++)
scanf("%d%d",&m[i].a,&m[i].b);
for (i=1;i<n;i++)
for (j=1;j<=n-i;j++)
if (m[j].a*m[j].b>m[j+1].a*m[j+1].b)
{
struct Node tmp;
tmp=m[j]; m[j]=m[j+1]; m[j+1]=tmp;
}
int p[4100]; // 保存累乘积,p[0]保存位数,p[1]~p[p[0]]保存从低位到高位数字
int t[4100]; // 中间结果
int ans[4100]; // 最终答案
ans[0]=1;
ans[1]=0;
p[0]=1;
p[1]=1;
for (i=1; i<=n; i++)
{
mul(p,m[i-1].a,t);
for (j=0; j<=t[0]; j++) p[j]=t[j];
div(p,m[i].b,t);
getMax(ans,t);
}
for (i=ans[0]; i>=1; i--)
printf("%d",ans[i]);
printf("\n");
return 0;
}
由于题目中a和b的值不超过10000,因此可以采用万进制进行乘法和除法的高精度运算,改写源程序如下。
(3)源程序2。
#include <stdio.h>
#include <string.h>
#define MOD 10000
struct Node
{
int a,b;
};
void mul(int *a,int b,int *c) // c=a*b
{
int len=a[0];
int i;
for (i=0;i<len+5;i++)
c[i]=0;
for (i=1; i<=len; i++)
{
c[i]+=(a[i]*b);
c[i+1]+=(c[i]/MOD);
c[i]%=MOD;
}
while (c[len+1]>0)
{
len++;
c[len+1]+=(c[len]/MOD);
c[len]%=MOD;
}
c[0]=len;
}
void div(int *a,int b,int *c) // c=a/b
{
int len=a[0];
int r=0; // 余数
int i;
for (i=len; i>=1; i--)
{
r=r*MOD+a[i];
c[i]=r/b;
r=r%b;
}
while (!c[len]) len--;
if (!len) len=1;
c[0]=len;
}
void getMax(int *x,int *y) // x=max(x,y)
{
if (y[0]<x[0]) return;
int i;
if (x[0]==y[0])
{
for (i=y[0]; i>=1; i--)
if (y[i]<x[i]) return;
else if (y[i]>x[i]) break;
}
for (i=0; i<=y[0]; i++)
x[i]=y[i];
}
int main()
{
int n;
scanf("%d",&n);
struct Node m[1005];
int i,j;
for (i=0; i<=n; i++)
scanf("%d%d",&m[i].a,&m[i].b);
for (i=1;i<n;i++)
for (j=1;j<=n-i;j++)
if (m[j].a*m[j].b>m[j+1].a*m[j+1].b)
{
struct Node tmp;
tmp=m[j]; m[j]=m[j+1]; m[j+1]=tmp;
}
int p[1100]; // 保存累乘积,每4位1组,p[0]保存位数
int t[1100]; // 中间结果
int ans[1100]; // 最终答案
ans[0]=1;
ans[1]=0;
p[0]=1;
p[1]=1;
for (i=1; i<=n; i++)
{
mul(p,m[i-1].a,t);
for (j=0; j<=t[0]; j++) p[j]=t[j];
div(p,m[i].b,t);
getMax(ans,t);
}
printf("%d",ans[ans[0]]);
for (i=ans[0]-1; i>=1; i--)
printf("%04d",ans[i]);
printf("\n");
return 0;
}
习题70
70-1 放棋子(1)
本题选自洛谷题库 (https://www.luogu.org/problem/P3182)
问题描述
给你一个N×N 的矩阵,每行有一个障碍,数据保证任意两个障碍不在同一行,任意两个障碍不在同一列,要求你在这个矩阵上放N枚棋子(障碍的位置不能放棋子),要求你放N个棋子也满足每行只有一枚棋子,每列只有一枚棋子的限制,求有多少种方案。
输入
第一行一个N,接下来一个N×N 的矩阵。N≤200,0 表示没有障碍,1 表示有障碍。
输出
一个整数,即合法的方案数。
输入样例
2
0 1
1 0
输出样例
1
(1)编程思路。
本题是一个典型的错排问题。
错排问题:有n个正整数1、2、3、…、n,将这n个正整数重新排列,使其中的每一个数都不在原来的位置上,这种排列称为正整数1、2、3、…、n的错排,问这n个正整数的错排种数是多少?
用递推的方法推导错排公式。
设将n个棋子放入矩阵中的n行(每行放一个)中,每行放的棋子与其障碍位置全不对应(均不放在障碍位置上)的方法数用D(n)表示,那么D(n-1)就表示将n-1个棋子放入n-1行中,每行放的棋子均不放在障碍位置上的方法数。
n个全部不放在障碍位置的棋子可以看成前n - 1个棋子放好后再放1个棋子,将最后1个棋子不放在障碍位置,不放在障碍位置的方式自然是与之前的棋子进行交换,交换的方式有两种:
1)在前n-1个全部不放在障碍位置的棋子中取任意一个棋子进行交换。n-1个棋子全部不放在障碍位置的方法数为D(n-1),在n-1个棋子中任取一个棋子的方法数为n-1,因此,这种情况下,方法数共有D(n-1)* (n-1)种。
2)在前n-1个棋子中,有n-2个棋子全部没有放在障碍位置,有1个棋子放在了障碍位置,取放在障碍位置的这一个棋子交换。n-1个棋子中中只有一个棋子放在障碍位置的方法数有n-1,其余n-2个棋子全部没有放在障碍位置的方法数有D(n-2), 因此,这种情况下,方法数共有D(n-1)* (n-1)种。
由此,可得错排的递推公式:D(n)=[D(n-2)+D(n-1)]*(n-1) 。
初始情况为:D(1)=0 (只有1个棋子没法放)
D(2)=1 (两个棋子全不放在障碍位置只有1种方法)
由于n值可以为200,此时方案数超过了长整数表示的范围,故采用高精度运算。
定义数组int d[201][500]={0};,其中d[i][0]保存d[i]的位数len,d[i][1]~d[i][len]从低位到高位保存d[i]的各位数字,每个数组元素保存1位数字。
(2)源程序。
#include <stdio.h>
int main()
{
int n;
scanf("%d",&n);
int i,j,k;
for (i=1;i<=n;i++)
for (j=1;j<=n;j++)
scanf("%d",&k);
int d[201][500]={0};
d[1][0]=1; d[1][1]=0;
d[2][0]=1; d[2][1]=1;
for (k=3;k<=n;k++)
{
for (i=1;i<=d[k-1][0];i++)
{
d[k][i]=d[k-1][i]+d[k-2][i];
d[k][i]*=(k-1);
}
int len=d[k-1][0]+4;
for (i=1;i<=len;i++)
{
if (d[k][i]>=10)
{
d[k][i+1]+=d[k][i]/10;
d[k][i]=d[k][i]%10;
}
}
while (len>0 && d[k][len]==0) // 去前置0
len--;
d[k][0]=len;
}
for (i=d[n][0];i>=1;i--)
printf("%d",d[n][i]);
printf("\n");
return 0;
}
70-2 放棋子(2)
问题描述
小虎刚刚上了幼儿园,老师让他做一个家庭作业:首先画3行格子,第一行有3个格子,第二行有2个格子,第三行有1 个格子。每行的格子从左到右可以放棋子,但要求除第一行外,每行放的棋子数不能超过上一行的棋子。第一行的棋子数不能为0,但剩下行可以为空。玩了一会儿,小虎对哥哥大虎说:”这个作业有很多种摆放法,我想找到,但我不知道有多少种方案,你能帮助我吗?”
大虎是学校信息学集训队的,立刻想到用计算机来解决这个问题,并很快有了解答:13。
第二天他把问题拿到学校,并说如果第一行有N 个格子,第二行有N−1 个格子,……,第 N行有1 个格子,怎么办?现在请你一块来帮助他解决这个难题。
输入
仅一行,一个正整数N。
输出
一行,方案总数。
输入样例
2
输出样例
4
(1)编程思路。
设f[i][j]表示第i行放j个棋子的方案数。
显然有 f[1][0]=1,f[1][1]=1,即第1行不放棋子和放1个棋子的方案数均为1。
F[i][0]=1,任何1行不放棋子的方法数为1。
当i=n时,
f[n][1]=f[n−1][1]+ f[n−1][0] // 第n行放1个棋子可看成第n-1行放1个棋子或不放棋子而得来
f[n][2]= f[n−1][0]+ f[n−1][1]+f[n−1][2] =f[n][1]+f[n-1][2]
……
f[n][k]= f[n−1][0]+f[n−1][1]+f[n−1][2]+⋯+f[n−1][k−1]+f[n-1][k]= f[n][k−1]+f[n−1][k]
即递推公式为:f[n][k] = f[n][k−1]+f[n−1][k] (0≤k≤n)
计算出所有的f[n][k]后,将f[n][1]~f[n][n]的值累加起来,就是n行放棋子的总方案数。
由于n值可以为100,此时方案数超过了长整数表示的范围,故采用高精度运算。
定义数组int f[101][101][61]={0};,其中f[i][j][0]保存f[i][j]的位数len,f[i][j][1]~f[i][j][len]从低位到高位保存f[i][j]的数字,每个元素保存1位数字。
(2)源程序1。
#include <stdio.h>
int max(int a,int b)
{
return a>b?a:b;
}
int f[101][101][61]={0};
int main()
{
int n;
scanf("%d",&n);
int i,j,k;
f[1][0][0]=1;
f[1][0][1]=1;
f[1][1][0]=1;
f[1][1][1]=1;
for (i=2;i<=n;i++)
{
f[i][0][0]=1;
f[i][0][1]=1;
for (j=1;j<=i;j++)
{
int len=max(f[i][j-1][0],f[i-1][j][0]);
int cf=0;
for (k=1;k<=len;k++)
{
f[i][j][k]=f[i][j-1][k]+f[i-1][j][k]+cf;
cf=f[i][j][k]/10;
f[i][j][k]=f[i][j][k]%10;
}
while (cf!=0)
{
f[i][j][++len]=cf%10;
cf/=10;
}
f[i][j][0]=len;
}
}
int ans[61]={0};
ans[0]=1;
ans[1]=0;
for (i=1;i<=n;i++)
{
int len=max(ans[0],f[n][i][0]);
int cf=0;
for (k=1;k<=len;k++)
{
ans[k]=f[n][i][k]+ans[k]+cf;
cf=ans[k]/10;
ans[k]=ans[k]%10;
}
while (cf!=0)
{
ans[++len]=cf%10;
cf/=10;
}
ans[0]=len;
}
for (i=ans[0];i>=1;i--)
printf("%d",ans[i]);
printf("\n");
return 0;
}
由于题目中进行的是大整数加法,故一个数组元素可以保存9位数字,这样进行高精度加法运算,既可以节省空间,也可以加快运算速度。改写源程序如下。
(3)源程序2。
#include <stdio.h>
#define MOD 1000000000
int max(int a,int b)
{
return a>b?a:b;
}
int f[101][101][8]={0};
int main()
{
int n;
scanf("%d",&n);
int i,j,k;
f[1][0][0]=1;
f[1][0][1]=1;
f[1][1][0]=1;
f[1][1][1]=1;
for (i=2;i<=n;i++)
{
f[i][0][0]=1;
f[i][0][1]=1;
for (j=1;j<=i;j++)
{
int len=max(f[i][j-1][0],f[i-1][j][0]);
int cf=0;
for (k=1;k<=len;k++)
{
f[i][j][k]=f[i][j-1][k]+f[i-1][j][k]+cf;
cf=f[i][j][k]/MOD;
f[i][j][k]=f[i][j][k]%MOD;
}
if (cf!=0)
f[i][j][++len]=cf;
f[i][j][0]=len;
}
}
int ans[8]={0};
ans[0]=1;
ans[1]=0;
for (i=1;i<=n;i++)
{
int len=max(ans[0],f[n][i][0]);
int cf=0;
for (k=1;k<=len;k++)
{
ans[k]=f[n][i][k]+ans[k]+cf;
cf=ans[k]/MOD;
ans[k]=ans[k]%MOD;
}
if (cf!=0)
ans[++len]=cf;
ans[0]=len;
}
printf("%d",ans[ans[0]]);
for (i=ans[0]-1;i>=1;i--)
printf("%09d",ans[i]);
printf("\n");
return 0;
}
70-3 三只小猪
问题描述
有三只小猪为了自己的安全,合伙建造了两个新砖房。怎样把三个小猪分配到两个房子里呢?第三只小猪是三只小猪中最聪明的一只,为了不浪费任何一个房子,它总共考虑了三种方案:
1)第1只小猪和第2只小猪同住一间,第3只小猪独自住一间;
2)第1只小猪和第3只小猪同住一间,第2只小猪独自住一间;
3)第2只小猪和第3只小猪同住一间,第1只小猪独自住一间。
“但是将来怎么办呢?”第三只小猪知道将来随着成员的增多,它们将会盖更多的房子。它想知道给定了房子和猪的数目后,房子的分配方案有多少?
输入
输入一行,包含两个整数n和m,分别表示小猪的数目和房间数(1≤n≤50,0≤m≤50)。
输出
输出一个整数,表示将n只小猪安置在m个房间且没有房间空闲的方案数。
输入样例
4 2
输出样例
7
(1)编程思路。
设f[n][m]表示n只小猪安置进m个房子里的方案数。
对于第n只小猪,有两种安排方法:1)该小猪独自住一个房间,则其他n-1只小猪安置进m-1个房子,方案数有f[n-1][m-1];2)该小猪在m个房间中选1间和其它的小猪合住,方案数有m*f[n-1][m]。
由此可得:f[n][m]=f[n-1][m-1]+m*f[n-1][m]
初始时,f[1][1]=1 。
由于数据范围较大,需要使用高精加和高精乘。由此每个元素f[i][j]用一个一维数组表示,可以定义为数组f[i][j][k],其中f[i][j][0]表示f[i][j]的位数,之后的f[i][j][1]~f[i][j][k] 表示从低位到高位的每一位。
(2)源程序。
#include <stdio.h>
#include <string.h>
int max(int a,int b)
{
return a>b?a:b;
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
if (n<m || m==0)
printf("0\n");
else
{
int f[55][55][61];
memset(f,0,sizeof(f));
int i,j,k;
for (i=1;i<=n;i++)
{
for (j=1;j<=m;j++)
{
if (i<j)
{
f[i][j][0]=1; f[i][j][1]=0;
}
else if (i==j)
{
f[i][j][0]=1; f[i][j][1]=1;
}
else
{
// f[n][m]=f[n-1][m-1]+m*f[n-1][m]
int len=max(f[i-1][j-1][0],f[i-1][j][0]);
for (k=1;k<=f[i-1][j][0];k++)
f[i][j][k]=j*f[i-1][j][k];
int cf=0;
for (k=1;k<=len;k++)
{
f[i][j][k]+=f[i-1][j-1][k]+cf;
cf=f[i][j][k]/10;
f[i][j][k]=f[i][j][k]%10;
}
while (cf!=0)
{
f[i][j][++len]=cf%10;
cf/=10;
}
f[i][j][0]=len;
}
}
}
for (i=f[n][m][0];i>=1;i--)
printf("%d",f[n][m][i]);
printf("\n");
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)