1. 课堂练习:最小周长
一个矩形的面积为S,已知该矩形的边长都是整数,求所有满足条件的矩形中,周长的最小值。例如:S = 24,那么有{1 24} {2 12} {3 8} {4 6}这4种矩形,其中{4 6}的周长最小,为20。
输入格式
输入1个数S(1 <= S <= 10^9)。
输出格式
输出最小周长。
输入样例
24
输出样例
20
2. 课堂练习:和为K的倍数
小b喜欢和为K的倍数的序列。
现在有一个长度为n的序列A,请问A有多少个非空连续子序列是小b喜欢的。
输入格式
第一行输入一个正整数n; 第二行输入n个整数,表示A[i],以空格隔开; 第三行输入一个正整数K; 其中1≤n≤30000,对于任意A[i]有-10000≤A[i]≤10000,2≤K≤10000
输出格式
输出一个数,表示子序列的数目
输入样例
6
4 5 0 -2 -3 1
5
输出样例
7
代码:
#include<bits/stdc++.h>
using namespace std;
int n, k;
int a[30005],b[30005]c[10];
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin >> a[i];
b[i]=a[i]+b[i-1];
}
cin >> k;
int sum =0;
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
if((b[i]-b[j-1])%k==0){
sum++;
}
}
}
cout<<sum;
}
但是由于计算次数过大,可以再次简化,用同余和桶排序思想;
改进代码2:
#include<bits/stdc++.h>
using namespace std;
int n, k;
int a[30005],b[30005],c[30005];
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin >> a[i];
b[i]=a[i]+b[i-1];
}
cin >> k;
int sum =0;
for(int i=1;i<=n;i++){
c[(b[i]%k+k)%k]++;
}
for(int i=0;i<k;i++){
sum+=c[i]*(c[i]-1)/2;
}
sum+=c[0];
cout<<sum;
}
对于余0 时只有一个要特殊考虑,他要算一个子集,因此第20行如此打;
对于同余的n个子集,共有C(n,2)的组合方式即:n*(n-1)/2种方式,最后加起来就是答案
此时为一层循环;
3.课后作业:数字1的数量
给定一个十进制正整数N,写下从1开始,到N的所有正数,计算出其中出现所有1的个数。
例如:n = 12,包含了5个1。1,10,12共包含3个1,11包含2个1,总共5个1。
输入格式
输入N(1 <= N <= 10^9)
输出格式
输出包含1的个数
输入样例
12
输出样例
5
思考:
1.不可行暴力代码:
直接mod的话由于N的取值范围为1-10^9;所以这样很容易超时;
所以考虑数学方法
2.真代码:
#include<bits/stdc++.h>
using namespace std;
int n;
int main(){
cin >>n;
int ans=0;
int cnt=0;//当前判断位数的后面位数如:1234正在判断2时,cnt为2;
int q=n;
while(n){
int k=n%10;//当前位数判断的数
if(k==0){
ans+=(n/10)*pow(10,cnt);//k为0时,他前面的数(n/10)只有n/10种情况
}
else if(k==1){
int h=pow(10,cnt);
ans+=(n/10)*pow(10,cnt)+((q % h)+1);
}
else{
ans+=((n/10)+1)*pow(10,cnt);//k为其他时,前面有n/10+1种方法,后面有10的cnt次方种方法,相乘即可得到这一分类下的答案
}
n/=10;
cnt++;
}
cout<<ans;
}
进阶习题:罐子和硬币
有n个罐子,有k个硬币,每个罐子可以容纳任意数量的硬币。罐子是不透明的,你可以把这k个硬币任意分配到罐子里。然后罐子被打乱顺序,你从外表无法区别罐子。最后罐子被编上号1-n。每次你可以询问某个罐子,如果该罐子里有硬币,则你可以得到1个(但你不知道该罐子中还有多少硬币),如果该罐子是空的,你得不到任何硬币,但会消耗1次询问的机会。你最终要得到至少c枚硬币(c <= k),问题是给定n,k,c,由你来选择一种分配方式,使得在最坏情况下,询问的次数最少,求这个最少的次数。
例如:有3个罐子,10个硬币,需要得到7个硬币,(n = 3, k = 10, c = 7)。
你可以将硬币分配为:3 3 4,然后对于每个罐子询问2次,可以得到6个硬币,再随便询问一个罐子,就可以得到7个硬币了。
输入格式
输入3个数:n,k,c (1 <= n <= 10^9, 1 <= c <= k <= 10^9)。
输出格式
输出最坏情况下所需的最少询问次数。
输入样例
4 2 2
输出样例
4
代码实现如下
#include<bits/stdc++.h>
using namespace std;
int n,k,c;
int main(){
int ans=0;
cin >>n>>k>>c;
int x=k/n;
int a=k%n;
int y=n-a;
if(a==0||x*n>=c){
cout<<c;
return 0;
}
else {
int ans1=c+y;
int z=k/(x+1);
int ans2=n-z+c;
cout<<min(ans1,ans2);
}
//关键在于放x(平均数向下取整)+1个硬币的罐头较多(ans2)还是放x个硬币的罐子较多(ans1),因为答案铁定是“摸空次数”+需要摸的硬币数(c)。
}
质数中的质数(质数筛法)
如果一个质数,在质数列表中的编号也是质数,那么就称之为质数中的质数。例如:3 5分别是排第2和第3的质数,所以他们是质数中的质数。现在给出一个数N,求>=N的最小的质数中的质数是多少(可以考虑用质数筛法来做)。
输入一个数N(N <= 10^6)
输出>=N的最小的质数中的质数。
输入样例
20
输出样例
31
1.一般方法
#include<bits/stdc++.h>
using namespace std;
int n,k,c;
bool prize(int x){
if(x==2){
return 1;
}
if(x==1||x==0){
return 0;
}
for(int i=2;i*i<=x;i++){
if(x%i==0){
return 0;
}
}
return 1;
}
int main(){
cin >>n;
int cnt=0;
for(int j=2;j<=n;j++){
if(prize (j)==1){
cnt ++;//前面多少个质数
}
}
// cout<<cnt<<" ";
if(prize(cnt)==1&&prize(n)!=1){
cnt++;
}
while(prize(cnt)!=1){
cnt++;//后面 质数编号 为cnt
}
// cout<<cnt<<" ";
int k=1;
while(cnt){
k++;
if(prize(k)==1){
cnt--;
}
}
cout<<k;
}
总结:由于循环过多,导致时间复杂度稍微有点大,所以考虑另一种算法
2欧拉筛法
#include<bits/stdc++.h>
using namespace std;
int sta[9999999];
bool pri[9999999];
int cnt;
int main(){
int n;
cin >> n;
if(n<=2){
cout<<"3";
return 0;
}
for(int i=2;i<=1000999;i++){
if(pri[i]==0){
sta[++cnt]=i;
if(pri[cnt]==0&&i>=n){
cout<< sta[cnt];
return 0;
}
}
for(int j=1;(j<=cnt)&&(i*sta[j]<=1000999);j++){
pri[i*sta[j]]=1;
if(i%sta[j]==0){
break;
}
}
}
}
思路是这样的:
1.每个数可表示为素数乘积:从2开始找,则有2 * 2(4)肯定不是素数,就将4排除在外,不必再考虑;同理,3 * 2(6)与3 * 3(9)也不在内;再往后遇到四,4 * 2(8)排除在外;就这样一直筛下去,就能以更少的时间复杂度找到所有素数;
2.为什么i%sta[j]==0就要break?
原因:因为后面会有其他数来筛掉他;例如i与sta[j]=3时,3 * 4会被4 * 3筛,为了节省时间复杂度,可以就在这里停下,避免重复筛选。
3.pri数组作用:确定某个数是否是被筛掉了的。
蚂蚁
课堂练习:蚂蚁 已完成
n只蚂蚁以每秒1cm的速度在长为Lcm的竿子上爬行。当蚂蚁爬到竿子的端点时就会掉落。由于竿子太细,两只蚂蚁相遇时,它们不能交错通过,只能各自反向爬回去。对于每只蚂蚁,我们知道它距离竿子左端的距离xi,但不知道它当前的朝向。请计算各种情况当中,所有蚂蚁落下竿子所需的最短时间和最长时间。
例如:竿子长10cm,3只蚂蚁位置为2 6 7,最短需要4秒(左、右、右),最长需要8秒(右、右、右)。
输入格式
第1行:2个整数N和L,N为蚂蚁的数量,L为杆子的长度(1 <= L <= 10^9, 1 <= N <= 50000) 第2 - N + 1行:每行一个整数A[i],表示蚂蚁的位置(0 < A[i] < L)
输出2个数,中间用空格分隔,分别表示最短时间和最长时间。
输入样例
3 10
2
6
7
输出样例
4 8
hp:
#include<bits/stdc++.h>
#include<cmath>
using namespace std;
double n,l;
double m=INT_MAX;
double p=INT_MIN;
double a[50005];
int main(){
cin >> n>>l;
for(int i=1;i<=n;i++){
cin>> a[i];
}
for(int i=1;i<=n;i++ ){
if(a[i]>p){
p=a[i];
}
if(a[i]<m){
m=a[i];
}
}
int k=max(p,l-m);//最大值
m=INT_MAX;
int t;
for(int i=1;i<=n;i++){
if(abs(a[i]-l/2)<m){
m=abs(a[i]-l/2);
t=i;
}
}
int g=min(l-a[t],a[t]);
cout<< int (1/2+g)<<" "<<int (1/2+k);
}
思路:涉及一点点贪心的想法:最短的时间很明显:在右侧全朝右边;在左侧的全朝左边;最短的时间就是距离中心最近的蚂蚁要出去的时间;
最长的时间:每只蚂蚁相同,所以两只蚂蚁碰到头转向可以视作蚂蚁穿过对方继续向前走;从而可以得到,站在距离两侧最近的蚂蚁到对面一边所要的时间就是最长时间。
距离之和最小(wa1/2)
X轴上有N个点,求X轴上一点使它到这N个点的距离之和最小,输出这个最小的距离之和。
输入格式
第1行:点的数量N。(2 <= N <= 10000) 第2 - N + 1行:点的位置。(-10^9 <= P[i] <= 10^9)
输出最小距离之和
输入样例
5
-1
-3
0
7
9
输出样例
20
#include<bits/stdc++.h>
using namespace std;
int n,a[1000005];
int k;
int main(){
cin >>n;
for(int i=1;i<=n;i++){
cin >>a[i];
}
sort(a+1,a+n+1);
int ans=0;
for(int i=1;i<=n;i++){
}
k=n/2+1;
for(int i=n;i>=k;i--){
ans+=a[i]-a[n-i+1];
}
cout<< ans;
}
小伙子不讲武德,只对了一半,只能提供思路:
就是最中间的点到其他点的距离,就是最小的;
呜呜呜过不了啊啊啊啊
K进制下的大数
有一个字符串S,记录了一个大数,但不知这个大数是多少进制的,只知道这个数在K进制下是K - 1的倍数。现在由你来求出这个最小的进制K。
例如:给出的数是A1A,有A则最少也是11进制,然后发现A1A在22进制下等于4872,4872 mod 21 = 0,并且22是最小的,因此输出k = 22(大数的表示中A对应10,Z对应35)。
输入大数对应的字符串S。S的长度小于10^5。
输出对应的进制K,如果在2 - 36范围内没有找到对应的解,则输出No Solution。
输入样例
A1A
输出样例
22
#include<bits/stdc++.h>
#include<cmath>
using namespace std;
string s;
int m=INT_MIN;
int main(){
cin >>s;
char o;
int len =s.size();
for(int i=0;i<len;i++){
if(int (s[i])>=65&&int(s[i])<=90){
if(int(s[i])-55>m){
m=int(s[i])-55;
}
}
else{
if(int(s[i])-48>m){
m=int(s[i])-48;
}
}
}
for(int g=m+1;g<=36;g++){
int sum =0;
for(int i=0;i<len;i++){
if(int(s[i])>=65&&int(s[i])<=90){
sum +=int(s[i])-55;
}
else{
sum +=int(s[i])-48;
}
}
if(sum%(g-1)==0){
cout<<g;
return 0;
}
}
cout<<"No Solution";
}
知识点:
1.大数所有数位的加和,如果是 k−1 的倍数,那么最终这个大数就是 k−1 的倍数
2.string的记录是从0开始的,即s[0]------s[len-1];
3.大写英文字母-55为它所代表的数字,在char下的数字-48为这个数字在int下的值。
空间换时间
即为提前将某数组预处理;
常见处理方法
1.前缀和:前面数之和
2.质数表:所有指数,可用欧拉筛
3.打表:预处理所有情况,避免重复
4.st表:对nlog(n)打表,每个表负责2 的k次方处理;两个st区间并起来表示任意一段区间
最少01翻转
小b有一个01序列,她每次可以翻转一个元素,即将该元素从0变1或者从1变0。
现在她希望序列不降,求最少翻转次数。
第一行输入一个数n,其中1≤n≤20000; 第二行输入一个由‘0’和‘1’组成的字符串
输出一个非负整数,表示翻转次数
输入样例
6
010110
输出样例
2
hp:
#include<bits/stdc++.h>
using namespace std;
char s[20005];
int a[20005],b[20005],c[20005];
int m=INT_MAX;
int main(){
int n;
cin >>n;
for(int i=1;i<=n;i++){
cin >> s[i];
}
s[n+1]='2';
for(int i=1;i<=n;i++){
if(s[i-1]=='1'){
a[i]=a[i-1]+1;
}
else{
a[i]=a[i-1];
}
}
for(int i=n;i>=1;i--){
if(s[i+1]=='0'){
b[i]=b[i+1]+1;
}
else{
b[i]=b[i+1];
}
}
for(int i=1;i<=n;i++){
c[i]=a[i]+b[i];
if(c[i]<m){
m=c[i];
}
}
cout<< m;
}
思路:利用前缀和与后缀和判断某一数位前面有多少个1,后面有多少个0(均不包含自身),然后两者相加得到如果这一个数不变的情况下反转的最小次数;最后再进行比较,得到其中最小的。
合为S
小b有一个 01 序列 A,她想知道 A 有多少个非空连续子序列和为 S。
你能帮帮她吗?
第一行输入一个数n,表示A的长度; 第二行输入n个数‘0’或‘1’,表示A中的元素,以空格隔开; 第三行输入一个非负整数S; 其中0≤S≤n≤30000。
输出一个数,表示子数组的个数
输入样例
5
1 0 1 0 1
2
输出样例
4
hp:
#include<bits/stdc++.h>
using namespace std;
int a[30005],b[30005];
int main(){
int n;
cin >> n;
for(int i=1;i<=n;i++){
cin >> a[i];
b[i]=a[i]+b[i-1];
}
int s;
cin >>s;
int cnt=0;
for(int i=1;i<=n;i++){
for(int j=i;j>=1;j--){
if(b[i]-b[i-j]==s){
cnt++;
}
}
}
cout<<cnt;
}
思路:利用前缀和思想,两个前缀和相减(后面-前面)可以表示为任意区间的和;就这样枚举找到个数即可。
与7无关的数
一个正整数,如果它能被7整除,或者它的十进制表示法中某个位数上的数字为7,则称其为与7相关的数。求所有小于等于N的且与7无关的正整数的平方和。
例如:N = 8,<= 8与7无关的数包括:1 2 3 4 5 6 8,平方和为:155。
输入格式
第1行:一个数T,表示后面用作输入测试的数的数量。(1 <= T <= 1000) 第2 - T + 1行:每行1个数N。(1 <= N <= 10^6)
输出格式
共T行,每行一个数,对应T个测试的计算结果。
输入样例
5
4
5
6
7
8
输出样例
30
55
91
91
155
题型:多次输入输出:打表
由于每次输入每次输出会占用很多时间,这种情况要考虑打表
#include<bits/stdc++.h>
using namespace std;
long long a[1000005],b[1005];
long long weishu(int x){
long long k=x;
if(x%7==0){
return 0;
}
else{
while(x){
if(x%10==7){
return 0;
}else{
x/=10;
}
}
return k*k;
}
}
int main(){
long long cnt=0;
int T;
cin >>T;
for(long long i=1;i<=1000005;i++){
a[i]=a[i-1]+weishu(i);
}
for(long long i=1;i<=T;i++){
cin >> b[i];
}
for(long long i=1;i<=T;i++){
cout<<a[b[i]]<<endl;
}
}
注意要点:
1.由于某个数是先天决定后天(如longlong 与int),所以这里k必须用long long 定义;
2.多次输入输出一定要注意endl
st表(全wa)
给出一个有N个数的序列,编号0 - N - 1。进行Q次查询,查询编号i至j的所有数中,最大的数是多少。
例如: 1 7 6 3 1。i = 1, j = 3,对应的数为7 6 3,最大的数为7。(该问题也被称为RMQ问题)
输入格式
第1行:1个数N,表示序列的长度。(2 <= N <= 10000) 第2 - N + 1行:每行1个数,对应序列中的元素。(0 <= S[i] <= 10^9) 第N + 2行:1个数Q,表示查询的数量。(2 <= Q <= 10000) 第N + 3 - N + Q + 2行:每行2个数,对应查询的起始编号i和结束编号j。(0 <= i <= j <= N - 1)
输出格式
共Q行,对应每一个查询区间的最大值。
输入样例
5
1
7
6
3
1
3
0 1
1 3
3 4
输出样例
7
7
3
#include<bits/stdc++.h>
using namespace std;
int a[10005][10005];
int k;
int c[10005],d[10005];
int e[10005];
int main(){
int n;
cin >> n;
for(int i=0;i<n;i++){
cin >> a[i][0] ;
}
for(int i=1;i<=n;i*=2){//i*=2就log2了
for(int j=0;j<n-pow(2,(i-1));j++){
a[j][i]=max(a[j][i-1],a[j+(1<<(i-1))][i-1]);//不用pow,不然会报错?
}
}
int q;
cin >> q;
for(int i=1;i<=q;i++){
cin >> c[i]>>d[i];
e[i]=log2(d[i]-c[i]);
}
for(int i=1;i<=q;i++){
cout<<max(a[c[i]][e[i]],a[d[i]-(1<<e[i])+1][e[i]])<<endl;
}
}
问题在哪里,我也不几道
最大子段和(wa1/2):
N个整数组成的序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的连续子段和的最大值。当所给的整数均为负数时和为0。
例如:-2,11,-4,13,-5,-2,和最大的子段为:11,-4,13。和为20。
输入格式
第1行:整数序列的长度N(2 <= N <= 50000) 第2 - N + 1行:N个整数(-10^9 <= A[i] <= 10^9)
输出格式
输出最大子段和。
输入样例
6
-2
11
-4
13
-5
-2
输出样例
20
#include<bits/stdc++.h>
using namespace std;
int a[50005],b[50005];
int m=INT_MIN;
int c[50005];
int main(){
int n;
int k;
bool g=0;
cin >> n;
for(int i=1;i<=n;i++){
cin >> a[i];
b[i]=b[i-1]+a[i];
c[i]=INT_MAX;//初值
if(a[i]>0&&g==0){
g=1;
k=i;
}
}
for(int i=0;i<=n;i++){
c[i]=min(c[i-1],b[i]); //c用来找某个前缀和前面的最小前缀和
}
if(g==0){//全是负数情况
cout<< 0;
return 0;
}else{
for(int i=k;i<=n;i++){
m=max(m,b[i]-c[i]); //b最大,c最小时,会有最大值,这就是为什么用了c数组,这样计算量会由O(n^2)降到O(2n)
}
}
cout<< m;
}
错了一些些,不知道问题在哪
肯定的是:如果像“合为S”那道题一个一个找,会超时。
复杂度优化技巧
重排列得到2次幂(改对啦)
小b有一个数n,现在她想把n的每一位重排列,使得得到的结果为2的幂次。
请问小b能得到2的幂次吗?
注意重排列后不允许有前导0。
样例解释:46重排列成64,为2^6。
输入一个数N,其中1≤N≤10^9
输出格式
满足条件,输出“true”; 不满足,则输出“false”。
输入样例
46
输出样例
true
我的思路是:找到这个数在2的n次方到2的m次方之间,再枚举这里面的数
#include <bits/stdc++.h>
using namespace std;
string s,t;
int main(){
//寻找这个数能组成的最大数与最小数在那些2^n之间
cin >> s;
t=s;
int len=s.size();
if(s[0]==49&&len==1){
cout<<"true";
return 0;
}
int ma=-10,mi=100;
for(long long i=0;i<len;i++){
ma=max(ma,int(s[i])-48);
if(int(s[i])!=48){
mi=min(mi,int(s[i])-48);
}
}
// cout<<mi<<""<<ma;
int k1=0,k2=0;
// cout<<(ma+1)*pow(10,len-1)<<" "<<mi*pow(10,len-1);
for(long long i=1;i<=(ma+1)*pow(10,len-1);i*=2){
k2++;
}
k1=k2;
for(long long i=pow(2,k2);i>=mi*pow(10,len-1);i/=2){
k1--;
}
int cnt;
// cout<<k1<<" "<<k2;
for(long long n=pow(2,k1);n<=pow(2,k2);n*=2){
long long p=n;
cnt=0;
s=t;
for(long long j=0;j<len&&p!=0;j++){
for(long long i=0;i<len;i++){
if(p%10==int(s[i])-48){
s[i]='p';
p/=10;
cnt++;
break;
}
}
}
if(cnt==len){
cout<<"true";
return 0;
}
}cout<<"false" ;
}