洛谷:P1036 P1032 P1021

P1036 [NOIP2002 普及组] 选数

题目描述

已知n 个整数 1,2,⋯ ,x1​,x2​,⋯,xn​,以及 1 个整数 k。从 n 个整数中任选k 个整数相加,可分别得到一系列的和。例如当 n=4,k=3,4 个整数分别为 3,7,12,19时,可得全部的组合与它们的和为:

3+7+12=22

3+7+19=29

7+12+19=38

3+12+19=34

现在,要求你计算出和为素数共有多少种。

例如上例,只有一种的和为素数:3+7+19=29

输入格式

第一行两个空格隔开的整数 n,k(1≤n≤20)。

第二行 n 个整数,分别为 1,2,⋯ ,x1​,x2​,⋯,xn​(1≤xi​≤5×106)。

输出格式

输出一个整数,表示种类数。

输入输出样例

输入 #1复制

4 3
3 7 12 19

输出 #1复制

1

说明/提示

【题目来源】

NOIP 2002 普及组第二题

解题思路:题意:n个数中挑k个数,记录出现和是素数的次数。

解题思路:现用欧拉筛,将题意范围内的素数筛出,然后用prime2进行记录。

然后开始从n个数中挑k个数,使用dfs,因为每个数只能用一次,所以dfs中是i+1,step记录当前一共挑了几个数,挑够k个的时候,判断是不是素数即。

#include<iostream>
#include<cstring>
using namespace std;
int n;
int k;
int ans = 0;
int a[25];
bool visa[25];
typedef long long ll;
ll prime[10000000];
ll prime2[10000000];
int countl = 0;
bool isprime[10000010];

void dfs(int now, int sum, int step) {
	if (step == k) {
		if (prime2[sum] == 1) ans++;
		return;
	}
	for (int i = now; i < n; i++) {
		dfs(i + 1, sum + a[i], step + 1);
	}
}
void oula() {
	memset(isprime, false, sizeof(isprime));
	for (ll i = 2; i <= 10000000; i++) {
		if (!isprime[i]) {
			prime[++countl] = i;
			prime2[i] = 1;
		}
		for (ll j = 1; j <= countl && i * prime[j] <= 10000000; j++) {
			isprime[i * prime[j]] = true;
			if (i % prime[j] == 0) break;
		}

	}
}

int main() {
	oula();
	cin >> n >> k;
	for (int i = 0; i < n; i++) cin >> a[i];
	dfs(0, 0, 0);
	cout << ans << endl;
}

P1032 [NOIP2002 提高组] 字串变换

题目背景

本题疑似错题,不保证存在靠谱的多项式复杂度的做法。测试数据非常的水,各种做法都可以通过,不代表算法正确。因此本题题目和数据仅供参考。

题目描述

已知有两个字串 A,B 及一组字串变换的规则(至多 66 个规则),形如:

  • A1​→B1​。
  • A2​→B2​。

规则的含义为:在 A 中的子串 A1​ 可以变换为 B1​,A2​ 可以变换为 B2​⋯。

例如:A=abcd,B=xyz,

变换规则为:

  • abc→xu,ud→y,y→yz。

则此时,A 可以经过一系列的变换变为 B,其变换的过程为:

  • abcd→xud→xy→xyz。

共进行了 3 次变换,使得 A 变换为 B。

输入格式

第一行有两个字符串 A,B。

接下来若干行,每行有两个字符串 Ai​,Bi​,表示一条变换规则。

输出格式

若在 1010 步(包含 1010 步)以内能将 A 变换为 B,则输出最少的变换步数;否则输出 NO ANSWER!

输入输出样例

输入 #1复制

abcd xyz
abc xu
ud y
y yz

输出 #1复制

3

说明/提示

对于 100%100% 数据,保证所有字符串长度的上限为 2020。

【题目来源】

NOIP 2002 提高组第二题

解题思路:题意:给你两个字符串 a 和 b,并且给你一些变换规则,判断需要多少次才能从a变到b;

思路:使用BFS,首先建一个队列(队列里,第一个值存当前的字符串,第二个值存此时一共变换的次数,然后每次查找当前元素中符合的变换条件,如果可以变换就找出从那些字符开始可以变换,并且变换,然后将新变化后的字符存入队列,变化次数+1。

因为同一个变换规则可能连续使用若干次,所以加了个while(1)的死循环,知道不能变换跳出,进行下一个变换规则。 但是使用stl会超时,所以可以使用结构体加手动模拟队列即可。

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
string a, b;
string ra[22], rb[22];
int i = 0;
queue<pair<string, int>>q;
int main() {
	cin >> a >> b;
	while (cin >> ra[i] >> rb[i]) i++;
	q.push({ a,0 });
	while (!q.empty()) {
		string cur = q.front().first;
		int ans = q.front().second;
		if (ans > 10) {
			cout << "NO ANSWER!" << endl;
			return 0;
		}
		for (int j = 0; j < i; j++) {
			int pos = cur.find(ra[j], 0);
			while (1) {
				if (pos == -1) break;
				else {
					ans++;
					cur.replace(pos, ra[j].size(), rb[j]);
					if (cur == b) {
						cout << ans << endl;
						return 0;
					}
					pos = cur.find(ra[j], pos + 1);
					q.push({ cur,ans });
				}
			}
		}
	}
	return 0;
}

ac代码

#include<iostream>
#include<cstring>
using namespace std;
string ra[22], rb[22];
int i = 0;
string a, b;
struct node {
	string cur;
	int sum;
}q[2000000];
int main() {
	cin >> a >> b;
	while (cin >> ra[i] >> rb[i]) i++;
	int head = 0, tail = 1;
	q[tail].cur = a;
	q[tail].sum = 0;
	while (head < tail) {
		head++;
		if (q[head].sum > 10) {
			cout << "NO ANSWER!" << endl;
			return 0;
		}
		for (int j = 0; j < i; j++) {
			int pos = q[head].cur.find(ra[j], 0);
			while (1) {
				if (pos == -1) break;
				else {
					tail++;
					q[tail].cur = q[head].cur;
					q[tail].sum = q[head].sum + 1;
					q[tail].cur.replace(pos,ra[j].size(),rb[j]);
					if (q[tail].cur == b) {
						cout << q[tail].sum;
						return 0;
					}
					pos = q[head].cur.find(ra[j],pos + 1);
				}
			}
		} 
	}
	return 0;
}

P1021 [NOIP1999 提高组] 邮票面值设计

题目描述

给定一个信封,最多只允许粘贴 N 张邮票,计算在给定 K(N+K≤15)种邮票的情况下(假定所有的邮票数量都足够),如何设计邮票的面值,能得到最大值 MAX,使在 1 至 MAX 之间的每一个邮资值都能得到。

例如,N=3,K=2,如果面值分别为 1 分、4 分,则在 1∼6分之间的每一个邮资值都能得到(当然还有 8 分、9 分和 12 分);如果面值分别为 1 分、3 分,则在 1∼7 分之间的每一个邮资值都能得到。可以验证当 N=3,K=2 时,7 分就是可以得到的连续的邮资最大值,所以MAX=7,面值分别为 1 分、3 分。

输入格式

2 个整数,代表N,K。

输出格式

输出共 2 行。

第一行输出若干个数字,表示选择的面值,从小到大排序。

第二行,输出 MAX=S,S 表示最大的面值。

输入输出样例

输入 #1复制

3 2

输出 #1复制

1 3
MAX=7

解题思路:思路:假设n=3,k=2; 面值分别为a[1] = 1, a[2] = 2;

则组成的最大面值为 a[k]*N。   组成的面值最大,则需要使用的邮票足够多。

所以可以假设f[i]为,组成面值为i的邮票所需的最少个数。

此时可以看出,f[1] = 1, f[2] =2,f[3] = f[3-2]+1 = 2(即组成3 需要 一个 2 一个 3,在(3-2)时候已经用掉了一张2,所以最后需要加1。f[4] = f[4-2]+1 = f[2] + 1 = 2, f[5] = f[5 - 2] +1 = f[3-2] + 1 +1 = 3 依次类推,f[i] = min(f[i],f[i-a[j]]+1);

加下来加上dfs搜索,dfs(x,mx) 分别为,当前是粘贴的第x张面值的邮票,在此之前组成的最大面值为mx。  所以当搜索到k = x 时技术搜索,此时即 k +1  = x;

每次搜索新面值的范围都需要比上一个面值大,所以是a[t-1]+1,但是又要比之前组成最大面值+1小,否则肯定不会连续。

#include<iostream>
#include<cstring>
using namespace std;
int n, k;
int ans[20], a[20]; // ans记录最大情况下k种面值,a是记录k种面值
int maxn = 0; // 最大的组合值
int dp(int t) {
	int f[10000]; // f[i]表示组成面值为i的最小数量
	for (int i = 0; i <= a[t] * n; i++) { // 初始化,i都是由面值为1的邮票组成
		f[i] = i; // f[i]表示组成面值为i的最小数量
	}
	for (int i = 1; i <= t; i++) { // 找出组成i需要的最少邮票
		for (int j = a[i]; j <= a[t] * n; j++) { //因为不可能找到比自己小的数,所以从自己开始找 
			f[j] = min(f[j], f[j - a[i]] + 1);
		}
	}
	for (int i = 1; i <= a[t] * n; i++) {
		if (f[i] > n) return i - 1; // 当找到需要的邮票数大于N,则返回前一个数即可。
	}
	return a[t] * n; // 都满足题意的时候,直接返回最大值即可。
}

void dfs(int t, int mx) {
	if (t == k + 1) { // 最多k种组合,k+1时证明已经有k种组合
		if (mx > maxn) {
			maxn = mx; // 替换最大值
			for (int i = 1; i <= t - 1; i++) ans[i] = a[i]; // 替换k种面值
		}
		return;
	}
	for (int i = a [t - 1]+ 1; i <= mx + 1; i++) {//继续找:为了避免重复,下一张邮票要比上一张邮票大,所以上边界是a[t-1]+1,同时它不能比最大连续值+1还大,不然最大连续值的下一个数就永远连不起来了 
		a[t] = i;
		int x = dp(t);
		dfs(t + 1, x); // 找下一张牌
	}
}

int main() {
	cin >> n >> k;
	dfs(1, 0);//先从第一张开始找,第一张前面没有数,所以所连续的最大数为 0 
	for (int i = 1; i <= k; i++) cout << ans[i] << " "; // 输出最大情况下的组合	
	cout << endl;
	cout << "MAX=" << maxn << endl;	// 最大组成的面值
	return 0;
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值