暴力枚举算法专项-题解(头都大了)
A - 一元三次方程求解
①【暴力枚举—出奇迹】
#include <iostream>#include <cstdio>using namespace std;int main(){double a,b,c,d;scanf("%lf%lf%lf%lf",&a,&b,&c,&d);for(double i=-100;i<=100;i+=0.001){double j=i+0.001;double y1=a*i*i*i+b*i*i+c*i+d;double y2=a*j*j*j+b*j*j+c*j+d;if(y1>=0&&y2<=0||y1<=0&&y2>=0){double x=(i+j)/2;printf("%.2lf ",x);}}}
②【二分】
因为区间很大,所以可以二分。
三个答案都在[-100,100]范围内,两个根的差的绝对值>=1,保证了每一个大小为1的区间里至多有1个解,也就是说当区间的两个端点的函数值异号时区间内一定有一个解,同号时一定没有解。那么我们可以枚举互相不重叠的每一个长度为1的区间,在区间内进行二分查找。
#include<cstdio>double a,b,c,d;double fc(double x){return a*x*x*x+b*x*x+c*x+d;}int main(){double l,r,m,x1,x2;int s=0,i;scanf("%lf%lf%lf%lf",&a,&b,&c,&d); //输入for (i=-100;i<100;i++){l=i;r=i+1;x1=fc(l);x2=fc(r);if(!x1){printf("%.2lf ",l);s++;} //判断左端点,是零点直接输出。//不能判断右端点,会重复。if(x1*x2<0) //区间内有根。{while(r-l>=0.001) //二分控制精度。{m=(l+r)/2; //middleif(fc(m)*fc(r)<=0)l=m;elser=m; //计算中点处函数值缩小区间。}printf("%.2lf ",r);//输出右端点。s++;}if (s==3)break;//找到三个就退出大概会省一点时间}return 0;}
B - Cantor 表
①【暴力求解法】
#include<iostream>int main(){long n,a=1,b=1,i=2;//i初始化必须为2,否则出错(当然你如果有解决办法1或0也可以)bool judge=true,become=false;//judge不得为false,否则一开始就错了std::cin>>n;while(i<=n){if(judge&&a==1){judge=false;b++;become=true;//这个变量是变换分母分子的关键点}else if(!judge&&b==1){judge=true;a++;become=true;}if(become)i++;//因为是替交,所以无需再像下面操作else if(judge){//分母增加分子减少a--;b++;i++;}else{//分子增加分母减少a++;b--;i++;}become=false;//不要忘,否则上面的if(become)一直为真则出错}std::cout<<a<<'/'<<b;return 0;}
②【找规律】
思路:
首先我们观察到第i行,第j列的数就是i/j这是第 一个要发现的。
因为题目中要求是以Z字型编号
我们看题目中的表是:
1/1,1/2,1/3 ……
2/1,2/2,2/3 ……
Z字型编号以后(把头向左偏45度):
第一行:1/1 (1号)
第二行:1/2 (2号) 2/1 (3号)
第三行:1/3 (6号) 2/2 (5号) 3/1 (4号)
↑ 观察法易得每一行比上一行多1
代码里那个while循环,就是为了通过循环枚举, 判断它在编号之后的第几行,第几个位置。
等差数列求和
公式:
%7D%7B2%7D%0A#card=math&code=S%3D%5Cfrac%7Bn%28a_n%2Ba_1%29%7D%7B2%7D%0A)
所以,很显然Z字型排序之后,第k行的数编号n满足:
%7D%7B2%7D%3Cn%E2%89%A4%20%5Cfrac%7Bk(k%2B1)%7D%7B2%7D%0A#card=math&code=%5Cfrac%7Bk%28k%E2%88%921%29%7D%7B2%7D%3Cn%E2%89%A4%20%5Cfrac%7Bk%28k%2B1%29%7D%7B2%7D%0A)
这样就可以把那个循环优化掉
#include <cstdio>using namespace std;int main() {int n,k=1;cin>>n;while (n>k) {n=n-k;k++;}if(k%2==0) cout<<n<<"/"<<(k+1-n);else cout<<k+1-n<<"/"<<n;return 0;}
代码当中k用来记Z字型编号之后的行数,显然第k行有k个数,因此每次n要减去k。
最后用k判断奇偶,是判断这一行Z字型编号是正序(类似第二行)还是倒序(类似第三行)然后用最开始的结论输出原表中的行号除以列号就行了
C - 不高兴的津津
#include<iostream>#include<cstdio>#include<cstdlib>using namespace std; //作为新手的我会的都写上int main (){int a,b,s,max=0,i,day=0; //a,b是我们津津(以下简称JJ)每天上课时间,s意为sum是上课时间之和for (i=1;i<8;i++) // i为循环变量,day是JJ一周最不高兴的一天{cin>>a>>b; //输入a,bs=a+b; //计算一天的上课时间if ((s>max)&&(s>8)) max=s,day=i; //在超过8小时且比之前几天都大的s时,将s赋给最大值,并记录下JJ的这天}cout<<day; //由于day初值是0,所以如果JJ一周都开心就输出0return 0;}
D - 铺地毯
其实这道题,有些题解的方法有些复杂。我们可以有一些投机取巧,因为,是看特定的某个位置上最后一块覆盖了的地毯。所以说,你可以从最后一个输入的数据开始排查,如果说你找到了这个点上面 有地毯,那么就直接输出这个值,如果没找到就按 照题干的意思输出-1。
#include<iostream>#include<cstdio>using namespace std;int shuzu[10001][10001];//自定义一个二维数组,内存看自己。int main(){int n,x,y;int b=1;cin>>n;for(int i=1;i<=n;++i){for(int ii=1;ii<=4;++ii)cin>>shuzu[i][ii]; //输入数据}cin>>x>>y;for(int q=n;q>=1;--q){if((shuzu[q][1]<=x)&&(shuzu[q][3]+shuzu[q][1]>=x)&&(shuzu[q][2]<=y)&&(shuzu[q][2]+shuzu[q][4]>=y)) //比较,如果包含了,就继续。{cout<<q;b=2 ;}if(b==2)break;if(q==1){cout<<-1;break;}}return 0;}
E - 三连击
暴力,加简化的判断,数学原理,2个集合内所有数相加相乘结果一样,2个集合的内容一样
①【代码1】
#include <stdio.h>int main(){int a,b,c;for(a=123;a<=333;a++){b=a*2;c=a*3;if((a/100+a/10%10+a%10+b/100+b/10%10+b%10+c/100+c/10%10+c%10==1+2+3+4+5+6+7+8+9)&&((a/100)*(a/10%10)*(a%10)*(b/100)*(b/10%10)*(b%10)*(c/100)*(c/10%10)*(c%10)==(1)*(2)*(3)*(4)*(5)*(6)*(7)*(8)*(9)))printf("%d %d %d\n",a,b,c);}return 0;}
②【代码二】
#include<cstdio>#include<cstring>int i,j,v;bool a[10];//ai表示第i个数已经用过了int main(){for(i=192;i<=327;i++)//第一个数最小192,最大327。其实不知道的情况下简单来说是从123-329的但是算出来是最值就稍微改了下下{memset(a,0,sizeof(a));v=0;//清零a[i%10]=a[i/10%10]=a[i/100]=a[i*2%10]=a[i*2/10%10]=a[i*2/100]=a[i*3%10]=a[i*3/10%10]=a[i*3/100]=1;//统计数字for(j=1;j<=9;j++) v+=a[j];//v表示1-9这些数字是否全部齐了if(v==9) printf("%d %d %d\n",i,i*2,i*3);//如果齐了就输出}return 0;}
F -最大公约数和最小公倍数问题(辗转相除法)
思路:把两数相乘,再遍历他的因子即可
#include<iostream>#include<cmath>using namespace std;typedef long long ll;int m,n,ans,flag;ll gcd(ll x,ll y){if(y==0) {return x;}return gcd(y,x%y);}int main(){cin>>n>>m;for(int i=1;i<=sqrt(1ll*m*n);i++){if((1ll*n*m)%i==0&&gcd(i,(1ll*n*m)/i)==n){ans++;if(1ll*i*i==1ll*n*m) flag=1;}}cout<<ans*2-flag;//最后乘以二是因为只遍历了一半return 0;}
G - 连续自然数和
特意去学了数学公式的代码 头都大了
设首项为 L,末项为 R,那么
%3D%20%5Cfrac%7B(L%2BR)(R%E2%88%92L%2B1)%7D%7B2%7D%3DM%0A#card=math&code=sum%28L%2CR%29%3D%20%5Cfrac%7B%28L%2BR%29%28R%E2%88%92L%2B1%29%7D%7B2%7D%3DM%0A)
即
(R-L%2B1)%3D2M%0A#card=math&code=%20%28L%2BR%29%28R-L%2B1%29%3D2M%0A)
可以把 2M分解成两个数之积,枚举假设分成了两个数 k1,k2,我们令 k1<k2,可以列一个二元一次方程组
解得
显然当k1 k2一奇一偶 时,L,R才是整数.
但是 L = R的情况是被不允许的,即
即
#include<cstdio>#include<iostream>using namespace std;int m;int main(){cin>>m;for(int k1=sqrt(2*m);k1>1;k1--)//枚举k1(注意是k1>1而不是k1>=1)if(2*m%k1==0 && (k1+2*m/k1)%2){//如果K2是整数而且与K1一奇一偶int k2=2*m/k1;cout<<(k2-k1+1)/2<<" "<<(k1+k2-1)/2<<endl;//输出答案}return 0;}
H - 约瑟夫
#include <stdio.h>int main(){int k,i;scanf("%d",&k);int flag=1,m=k;while (flag){m++;//m至少为k+1int cursor=0;//设置光标的移动for (i=0; i<k; i++){cursor=(cursor+m-1)%(2*k-i);//下一次出列之人的编号if (cursor<k)break;//判断出列的那个人是否小于k,小于就进行下一个m的判断if (i==k-1)flag=0;//说明已经找到最小的m}}printf("%d\n",m);return 0;}
I - 子数整除
暴力就是最优解
#include<cstdio>#include<iostream>using namespace std;bool f;//f用来判断是否有符合条件的数int main(){int k,a1;cin>>k;for(int i=10000;i<=30000;i++){if(i/100%k==0)//判断1到3位if((i/10-i/10000*1000)%k==0)//判断2到4位if((i-i/1000*1000)%k==0) cout<<i<<endl,f=1;//判断2到5位,若都符合,输出该数,f为真,继续搜索}if(!f) cout<<"No";return 0;}
J - 手机
暴力枚举
#include<iostream>#include<string>#include<cstring>using namespace std;int a[10005],totals=0;string s;int main(){//这......就是我的打表大法//把每一个字母对应的ascll码值在数组中的位置标记成需要按多少次a[int('a')]=1;a[int('b')]=2;a[int('c')]=3;a[int('d')]=1;a[int('e')]=2;a[int('f')]=3;a[int('g')]=1;a[int('h')]=2;a[int('i')]=3;a[int('j')]=1;a[int('k')]=2;a[int('l')]=3;a[int('m')]=1;a[int('n')]=2;a[int('o')]=3;a[int('p')]=1;a[int('q')]=2;a[int('r')]=3;a[int('s')]=4;a[int('t')]=1;a[int('u')]=2;a[int('v')]=3;a[int('w')]=1;a[int('x')]=2;a[int('y')]=3;a[int('z')]=4;getline(cin,s);for(int i=0;i<=s.size()-1;i++){if(s[i]==' ')//空格特殊处理{totals+=1;}else{totals+=a[int(s[i])];}}cout<<totals;return 0;}
K - 回文日期
枚举后面四位(月份+日期)会更快。
枚举后四位然后求出整个日期,判断是否在范围内即可。
2月不需要判断是否是闰年,因为0229反过来是9220,整个日期是92200229,而9220年是闰年
#include<cstdio>#include<iostream>using namespace std;int a, b, ans;//a,b=从a->b中的年份,ans=计数器(有几个回文年数)int x, y;//从x年至y年int t[1000000], q[25] = { 31,29,31,30,31,30,31,31,30,31,30,31 };//一年的月份int hw(int u)//对数字u进行反序处理{int k = 0;while (u != 0){k = k * 10 + u % 10;//每次加新数时,k往前挪一个位置u /= 10;}return k;}int main(){//20191101int i, j, k1, k2, k;cin >> a >> b;x = a % 100000 / 10000 + a % 1000000 / 100000 * 10 + a % 10000000 / 1000000 * 100 + a % 100000000 / 10000000 * 1000;//算出是几几年y = b % 100000 / 10000 + b % 1000000 / 100000 * 10 + b % 10000000 / 1000000 * 100 + b % 100000000 / 10000000 * 1000;//同理//对x->y年进行枚举for (i = x; i <= y; i++)//年for (k1 = 1; k1 <= 12; k1++)//月for (k2 = 1; k2 <= q[k1 - 1]; k2++)//日{// cout<<i<<" "<<k1<<" "<<k2<<endl;k = i * 10000 + k1 * 100 + k2;//构成这天用8位数表达方式if (k == hw(k))//判断是否回文ans++;}cout << ans;//输出return 0;//好习惯}
L- 棋盘问题
①【暴力枚举】
#include<cstdio>#include<iostream>using namespace std;int m, n, z, c, i, j, k, l; //z用来记正方形个数,c来记长方形int main(){cin >> m >> n; //输入for (i = 0; i <= m; i++) //枚举for (j = 0; j <= n; j++) //枚举for (k = i + 1; k <= m; k++) //还是枚举for (l = j + 1; l <= n; l++) //仍然是枚举if (k - i == l - j)z++; //是正方形else c++; //是长方形cout << z << " " << c; //输出}
②【找规律】
说说公式是怎么推导的吧
找规律:
正方形:
边长为1的正方形个数为n*m
边长为2的正方形个数为(n-1)*(m-1) (自己动手想想)
边长为3的正方形为个数(n-2)*(m-2)
边长为min(n,m)的正方形为个数s1=(n-min(n,m)+1)*(m-min(n,m)+1)
然后从边长为1到min(m,m)的正方形个数全部加起来;
长方形:(包括正方形,好像正方形属于长方形来着?)
长为1的长方形(包括正方形)有n个
长为2的长方形(包括正方形)有n-1个
长为n的长方形(包括正方形)有1个
长为1到n的长方形1+2+…+n个
同理 宽为1的长方形(包括正方形)有m个
宽为2的长方形(包括正方形)有m-1个
宽为m的长方形(包括正方形)有1个
宽为1-m的长方形1+2+…+m个
然后把它们乘起来,根据乘法原理,总数s2=((1+n)_(1+m)_n*m)/4
题目要求的是“非正方形的长方形”,因此要减去s1
#include<cstdio>#include<iostream>using namespace std;int main(){int n, m, s1 = 0, s2;cin >> n >> m;s2 = ((m + 1)*(n + 1)*m*n) / 4;for (; m >= 1 && n >= 1; m--, n--)s1 += m * n;cout << s1 << " " << s2 - s1;return 0;}
能看到这里 说明你非常认真 加油!!!
