暴力枚举9-12

在这里插入图片描述
这道题的思路是:遍历每一个点,然后以这个点为起点,遍历这个点的下方和右边,如果k个满足条件的点,就使sum+1.。。

#include<iostream>
#include<cstdio>
using namespace std;
char map[1000][1000];
long long int sum = 0;
int R, C, K;
void dfs(int i,int j)
{
	if (i > R || j > C)
		return;
	if (map[i][j] == '.')
	{
		int temp1 = i;
		int temp2 = j;
		int x;
		for (x = 1; x <= K; x++)  //遍历一下列能不能站人
		{
			if (map[temp1][temp2] != '.')
				break;
			else
				temp1 += 1;
		}
		if (x == (K + 1))
			sum += 1;
		temp1 = i;   //恢复状态
		for (x = 1; x <= K; x++)  //遍历一下列能不能站人
		{
			if (map[temp1][temp2] != '.')
				break;
			else
				temp2 += 1;
		}
		if (x == (K + 1))
			sum += 1;
	}
	if (i + 1 > R)
	{
		dfs(1, j+1);
	}
	else
	{
		dfs(i+1, j);
	}
}
int main()
{
	cin >> R >> C >> K;
	for (int i = 1; i <= R; i++)
		for (int j = 1; j <= C; j++)
			cin >> map[i][j];
	dfs(1, 1);
	if (k == 1)   //如果k==1,那么会重复运算,要除以2
		sum /= 2;
	cout << sum;
	return 0;
}

在这里插入图片描述

  1. 写这个题的方法有很多,其中可以暴力解决且AC的就是打表法,关于打表法明天会写一个专题专门来介绍。
  2. 那么正常来说,如果对[a,b]区间每一个数进行枚举,然后判断是不是回文,再判断是不是质数,这样的方法肯定是超时的,毫无技巧可言。那么将毫无技巧可言的代码也放上来。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int arr[10000];
int R = 0;
int judge(int a)  //判断素数
{
	if (a == 2)
		return 1;
	for (int i = 2; i <= sqrt(a); i++)
	{
		if (a % i == 0)  //不是素数
			return 0;
	}
	return 1;
}

void exchange_string(int a)   //数字转换成字符串
{
	memset(arr, 0, sizeof(arr));
	int temp = 10;
	int i = 0;
	while (a > 0)
	{
		arr[i] = a - a / temp*temp;
		a = a / temp;
		R += 1;
		i += 1;
	}
}
int judge1(int a)   //判断回文
{
	//将a导进来,然后将a转换成字符串的形式,然后用双指针遍历。
	exchange_string(a);
	int left = 0;
	int right = R - 1;  //R用过就要恢复状态
	R = 0;
	while (right > left)
	{
		if (arr[left] == arr[right])
		{
			left += 1;
			right -= 1;
		}
		else
			break;
	}
	//有两种情况,要么left指针等于right指针,这是回文串是奇数的时候。如果回文串是偶数,那么right再left左边一个单位
	if (right == left || (right + 1) == left)
		return 1;
	else
		return 0;
}


int main()
{
	int a, b;
	cin >> a >> b;   //找出[a,b]所有的回文数,且该数是质数
	for (int i = a; i <= b; i++)
	{
		if (judge(i) == 1)  //先判断质数
		{
			if(judge1(i)==1)  //再判断回文,实际上先判断回文快一点
				printf("%d\n", i);
		}
	}
	return 0;
}

这个代码的分数是66分,离100分ac还差很多。但是证明这个思路没有错,关键就是怎样去减少循环的次数,这也是这道题的精髓!

  1. 位数是偶数的回文数一定不是素数(质数)
  2. 质数一定不能被2整除,即奇数不一定是质数,但偶数一定不是质数

那么通过这两点的认知,我们就可以很容易写出更简单的代码

首先,我们要知道怎么去判断质数,这里先介绍最简单的方法

bool judge_sushu(int target)
{
	if (target == 2)  //如果是2,那就是素数,直接返回1,
		return true;
	for (int i = 2; i <= sqrt(target); i++)
	{
		if (target % i == 0)  //如果可以被i整除,说明这个数不是素数,返回false
			return false;
	}
	return true;
}

然后要知道怎么判断回文

bool judfe_huiwen(int target)  //判断回文
{
	int temp = target;
	int re_target = 0;
	while (temp > 0)
	{
		re_target = re_target * 10 + temp%10;   //取temp最后一个数字,反着取给re_target,自己去拿12321模拟一下过程
		temp /= 10;
	}
	if (target == re_target)  //如果正的等于反的数,那么就是回文数
		return true;
	else              //否则不是
		return false;
}

再一个,偶数位的回文串都不是素数。

bool  judge_weishu(int target)
{
	int k = 1;
	while (target > 0)  //计算位数
	{
		if (target / 10 >= 1)
			k += 1;
		target /= 10;
	}
	if (k % 2 == 0&&k>2)  //如果位数是偶数,那么返回false,k>2是因为11也是质数,要特殊处理
		return false;
	else              //否则返回true
		return true;
}

知道这些代码的原理,那么可以写出完整的程序了,见下面:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;

bool judge_sushu(int target)
{
	if (target == 2)  //如果是2,那就是素数,直接返回1,
		return true;
	for (int i = 2; i <= sqrt(target); i++)
	{
		if (target % i == 0)  //如果可以被i整除,说明这个数不是素数,返回false
			return false;
	}
	return true;
}

bool judge_huiwen(int target)  //判断回文
{
	int temp = target;
	int re_target = 0;
	while (temp > 0)
	{
		re_target = re_target * 10 + temp%10;   //取temp最后一个数字,反着取给re_target,自己去拿12321模拟一下过程
		temp /= 10;
	}
	if (target == re_target)
		return true;
	else
		return false;
}

bool judge_weishu(int target)
{
	int k = 1;
	while (target > 0)  //计算位数
	{
		if (target / 10 >= 1)
			k += 1;
		target /= 10;
	}
	if (k % 2 == 0&&k>2)  //如果位数是偶数,那么返回false
		return false;
	else              //否则返回true
		return true;
}

int main()
{
	int a, b;
	cin >> a >> b;
	b = (b < 9999999) ? b : 9999999;  //因为最大到9999999,因为题目最大位1亿,所以最大的回文99999999,而千万级别又是偶数位,所以所有千万级别都要排除,所以回文的范围是百万级别,所以最大是9999999。
	if (a % 2 == 0)//先找第一个奇数,这一步的目的就是因为质数不能被2整除,后面遍历的时候从一个奇数开始,然后每次遍历+2
		a = a + 1;
	for (int i = a; i <= b; i+=2)
	{
		if (judge_huiwen(i) == true)  //先判断回文
		{
			if (judge_weishu(i) == true)
			{
				if (judge_sushu(i) == true)
				{
					printf("%d\n", i);
				}
			}
		}
	}
	return 0;
}

这样已经可以ac满分了,但是有没有更好的方法呢?答案是有的。这要用到一个数学小理论:

埃拉托斯特尼筛选法,我们简单一点,叫“埃氏筛选法”
埃式筛选法:如果一个数 x 已经遍历过,不管x是不是素数,它的倍数一定不是素数。
比如:2是素数,那么4,6,8,10.。。。。2n都不是素数
又比如:3是素数,那么6,9,13,15,18,21,,,,3n都不是素数。
很好理解,那么根据这个原理,我们可以很容易写出代码

void judge_sushu(int a, int b)
{
	//先假设所有的数都是不是素数。那么这些数的倍数一定不是素数。也就是说,先把大于等于2的值都赋值0,代表都是合数
	//然后把他们的倍数置为1,那么之后遍历for循环的时候,只要prime[i]==1,那么一定不是质数,但是prime[i]==0是不是质数还不确定,所以仍然
	//需要判断,所以这里假设prime[i]是合数不是因为它真的是合数,因为不管它是不是合数,他的倍数一定不是质数。而且后面的判断也只看prime[i]==1.
	for (int i = a; i <= b; i++)
	{
		if (prime[i] == 0)
		{
			for (int j = i * 2; j <= b; j = j + i)
				prime[j] = 1;
		}
	}
}

将这个代码加入程序,也可以ac,且时间更快,属于空间换时间了。毕竟一个char数组很大

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
char prime[100000000];
bool judge_sushu(int target)
{
	if (target == 2)  //如果是2,那就是素数,直接返回1,
		return true;
	for (int i = 2; i <= sqrt(target); i++)
	{
		if (target % i == 0)  //如果可以被i整除,说明这个数不是素数,返回false
			return false;
	}
	return true;
}

void judge_sushu_1(int a, int b)
{
	//先假设所有的数都是不是素数。那么这些数的倍数一定不是素数。也就是说,先把大于等于2的值都赋值0,代表都是合数
	//然后把他们的倍数置为1,那么之后遍历for循环的时候,只要prime[i]==1,那么一定不是质数,但是prime[i]==0是不是质数还不确定,所以仍然
	//需要判断,所以这里假设prime[i]是合数不是因为它真的是合数,因为不管它是不是合数,他的倍数一定不是质数。而且后面的判断也只看prime[i]==1.
	for (int i = a; i <= b; i++)
	{
		if (prime[i] == 0)
		{
			for (int j = i * 2; j <= b; j = j + i)
				prime[j] = 1;
		}
	}
}

bool judge_huiwen(int target)  //判断回文
{
	int temp = target;
	int re_target = 0;
	while (temp > 0)
	{
		re_target = re_target * 10 + temp%10;   //取temp最后一个数字,反着取给re_target,自己去拿12321模拟一下过程
		temp /= 10;
	}
	if (target == re_target)
		return true;
	else
		return false;
}


bool judge_weishu(int target)
{
	int k = 1;
	while (target > 0)  //计算位数
	{
		if (target / 10 >= 1)
			k += 1;
		target /= 10;
	}
	if (k % 2 == 0&&k>2)  //如果位数是偶数,那么返回false
		return false;
	else              //否则返回true
		return true;
}

int main()
{
	int a, b;
	cin >> a >> b;
	b = (b < 9999999) ? b : 9999999;  //因为最大到9999999
	judge_sushu_1(a, b);
	if (a % 2 == 0)//先找第一个奇数,这一步的目的就是因为质数不能被2整除,后面遍历的时候从一个奇数开始,然后每次遍历+2
		a = a + 1;
	for (int i = a; i <= b; i+=2)
	{
		if (prime[i] == 1)  //如果是合数,跳出
			continue;
		if (judge_huiwen(i) == true)  //先判断回文
		{
			if (judge_weishu(i) == true)
			{
				if (judge_sushu(i) == true)  //这一步还是不能少,因为prime[i]==1代表是合数,但是prime[1]==0不代表就是素数。一定搞清楚
				{
					printf("%d\n", i);
				}
			}
		}
	}
	return 0;
}

在这里插入图片描述

这道题用两种方法都可以
1:dfs
2.不用dfs

1.首先,我们知道,所有的由火柴拼出来的数字归根结底都是0-9这10个数字,那么就先预处理一下这10个数字

int a[10] = { 6,2,5,5,4,5,6,3,7,6};  //摆放0-9需要的火柴数

2.这个时候有人问了,那10以后的数怎么办呢?这就是这道题的关键,还要对大于10的数进行处理(这里以2000为上限,大一点好)

for (int i = 1; i <= 2000; i++)   //把数字转换成火柴数
	{
		int temp = i;  //把i先临时存进一个遍历temp
		while (temp > 0)  //然后一位一位计算需要多少火柴
		{
			num[i] += a[temp % 10];   //temp%10就是当前temp的最后一个数字
			temp /= 10;
		}
	}

完整代码:

#include<iostream>
#include<cstdio>
using namespace std;
int a[10] = { 6,2,5,5,4,5,6,3,7,6};  //摆放0-9需要的火柴数
int num[2001];
int path[10];
int n;
int sum = 0;
/*void dfs(int k)  //回溯
{
	if (k > 3)
	{
		if (path[1] + path[2] == path[3]&&(n-4)==0)
		{
			sum += 1;
		}
		return;
	}
		
	for (int i = 0; i <= 1000; i++)
	{
		if (n - num[i] >= 4)  //还有火柴
		{
			path[k] = i;
			n = n - num[i];
			dfs(k + 1);
			n = n + num[i];  //恢复状态
		}
	}
}*/
int main()
{
	cin >> n;
	num[0] = 6;
	for (int i = 1; i <= 2000; i++)   //先把数字转换成火柴数
	{
		int temp = i;
		while (temp > 0)
		{
			num[i] += a[temp % 10];
			temp /= 10;
		}
	}
	//dfs(1);
	for (int i = 0; i <= 1000; i++)
	{
		for (int j = 0; j <= 1000; j++)
		{
			if (num[i] + num[j] + num[i + j] == n - 4)
				sum += 1;
		}
	}
	cout << sum;
}

用递归的话就把上面代码灰色的注释取消,让主函数的for循环注释掉就好了。(dfs(1)下面的for循环注释掉)

在这里插入图片描述

这道题的思路是枚举所有长度,以这个长度为长边,然后再以这个长边为基础取取短边。这里有几个基础

1.要知道组合的实现

int C(int n, int m)  //求n个数里面拿m个数有多少种组合
{
	int ans = 1;
	for (int i =1; i<=m; i++)
	{
		ans = ans * n / i;
		n -= 1;
	}
	return ans;
}

这个代码的原理就是高中数学学的组合。

c(n,m)n在下面,m在上面,比如c(6,2),m是2,n是6。
c(6,2)等于(65)/(12)…公式是:(n* n-1 * n-2 * … * n-m+1)/(m!);

那么有人会问,因为在c里面‘/’是取整除法,万一上面的代码出现小数岂不是出错?这个问题问的很好,非常细心。那么下面就这个问题来证明一下。

首先,我们发现,任意两个连续的数字,其中必有一个可以被2整除。

假设两个连续的数分别为m,m+1.
m%2只有两种情况。
1.m%2=0,则m是2的倍数,m可以被2整除
2.m%2=1,则m=2*k+1,则m+1=2 * k+1+1=2 * (k+1),则m+1是2的倍数,m+1可以被2整除

由此推导,那么3个连续的数必然有一个数可以被3整除

假设三个连续的数分别是m,m+1,m+2;
m%3只有三种可能
1.m%3=0,则m是3的倍数,m可以被3整除
2.m%3=1,则m=3 * k+1,则m+2=3 * k+3=3 * (k+1).则m+2可以被3整除
3.m%3=2,则m=3 * k+2,则m+1=3 * k+3=3 * (k+1),则m+1可以被3整除

那么大胆推导一下,n个连续的数相乘一定可以被n整除!

但是这还不够,再想一想。

n个连续的数相乘可以被1到n的所有数整除!
比如三个连续的数即可以被3整除,同时也可以被2整除,也可以被1整除
还是m,m+1,m+2
m%2两种可能。
1.m%2=0,则m可以被2整除
2.m%2=1,则m+1可以被2整除

还不够!还可以推导💥
再大胆猜测

n个连续的数相乘可以被1到n的所有数同时整除!
这个想了半天,还是证明不完整,还是当成结论记吧。。。

知道这个组合的构造就好写了

#include<iostream>
#include<cmath>
#include<cstdio>
#pragma warning (disable:4996)
using namespace std;
int num_count[10000];   //num_count[i]==j代表长度为i的边长有j个
const int kmod = 1e9 + 7;
long long int C(int a, int b)   //求a个数里面取b个数德所有组合,当然a要大于等于b,在这道题里面b要么是1,要么是2
{
	if (b==1)
		return a;
	else
		return (a * (a - 1) / 2);
}
int main()
{
	int n;
	int a;
	int max = 0;   //记录最长的边
	int min = 1000000;   //记录最短的边
	long long int sum = 0;
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		scanf("%d", &a);
		if (a > max)
			max = a;
		if (min > a)
			min = a;
		num_count[a] += 1;
	}
	for (int i = min; i <= max; i++)  //枚举所有长度的边
	{
		if (num_count[i] >= 2)  //如果该长度的边的个数大于2个,那么把这边当作长边
		{
			//先算出这两条边的所有组合
			int k1 = C(num_count[i], 2);
			for (int j = min; j <= i / 2; j++)   //因为先枚举的长边,那么就再枚举短边,短边有两条,两条之和等于长边
			{
				if (i - j == j&&num_count[j]>=2)//两条短边一样长且短边有两条以上
				{
					int k2 = C(num_count[j], 2);
					sum = sum + k1 * k2 % kmod;
				}
				if (i - j != j && num_count[j] >= 1 && num_count[i - j] >= 1)
				{
					int k2 = C(num_count[j], 1);
					int k3 = C(num_count[i - j], 1);
					sum = sum + k1 * k2 * k3 % kmod;
				}
				sum%= kmod;
			}
		}
	}
	printf("%lld", sum);
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值