被迫做的笔记

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" ;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值