P1036 选数
题目大意
已知 n n n 个整数,从中选出 k k k 个数进行相加,统计总和是素数的组合一共有多少种。
注意:
-
n ≤ 20 n\le 20 n≤20,选数是组合,不考虑顺序;
-
vis 只能保证:一条递归链条里面不会重复选同一个元素。
但是不能阻止不同顺序挑选同一批元素,产生重复方案。
-
同一个数字只能选一次;
-
不需要输出具体是哪些数,只输出满足条件的方案数量。
样例:
输入 (n=4,k=3),数字:3 7 12 19
所有3数组合:
(3+7+12=22)(不是素数)
(3+7+19=29)(素数)
(3+12+19=34)(不是素数)
(7+12+19=38)(不是素数)
满足条件只有1种,输出1。
核心思路
DFS回溯枚举所有选 k k k个数的组合 + 素数判断
#include<bits/stdc++.h> using namespace std; #define int long long #define endl '\n' #define pii pair<int,int> #define fi first #define se second const int N=101; int n,k; vector<int>a; //vector<bool>vis; int ans=0; bool check(int x){ if(x==2)return true; else if(x<2)return false; else if(x%2==0)return false; else{ for(int i=3;i*i<=x;i+=2){ if(x%i==0) return false; } } return true; } //pos:当前遍历到数组第几个位置 //cnt:已经选了多少个数 //sum:已经选出数字的累加和 void dfs(int pos,int step,int sum){ if(step==k){ if(check(sum)) ans++; return; } for(int i=pos;i<=n;i++){ dfs(i+1,step+1,sum+a[i]); //vis[i]=false; } } void slove(){ cin>>n>>k; a.resize(n+1); //vis.resize(n+1); ans=0; for(int i=1;i<=n;i++){ cin>>a[i]; } dfs(1,0,0); cout<<ans<<endl; } signed main(){ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); int _=1; //cin>>_; while(_--) slove(); return 0; }
P1032 [NOIP 2002 提高组 字串变换(疑似错题) - 洛谷](https://www.luogu.com.cn/problem/P1032)
P1032 字串变换
题目大意
已知初始字符串 A A A,目标字符串 B B B,最多给出6条变换规则 A i → B i A_i \to B_i Ai→Bi:把字符串里的子串 A i A_i Ai替换成 B i B_i Bi。
每次变换,选取当前串中任意一处匹配 A i A_i Ai的子串,执行替换。
求从 A A A变为 B B B最少需要几步;步数不能超过10步(包含10) 。超过10步还没得到 B B B,输出 NO ANSWER!。
样例:
A=abcd,B=xyz
规则:
abc→xu,ud→y,y→yz变换:
abcd → xud → xy → xyz,一共3步,输出3。
约束:所有字符串长度最大20,全部小写字母。
核心思路
求最少变换步数 → 典型最短路问题,每一次字符串变换是一条边,字符串是节点,用==BFS广度优先搜索==。
- BFS队列存储:
(当前字符串,已经变换的步数)。 - 开
unordered_set记录已经访问过的字符串,防止重复入队(死循环,例如a→b,b→a来回变换)。 - 每次取出队首元素:
- 如果当前字符串等于目标 B B B,直接返回当前步数。
- 如果步数 ≥ 10 \ge 10 ≥10,不再继续扩展,不能再往下走。
- 遍历全部6条替换规则,对当前字符串,找到所有可以匹配该规则的子串位置,每一个位置替换生成新字符串。
- 如果新字符串没有被访问过,标记访问,入队,步数+1。
- 队列为空还没找到目标串,输出
NO ANSWER!。
样例模拟
输入:
start = "abcd",target = "xyz"
规则:
R1: "abc" → "xu"
R2: "ud" → "y"
R3: "y" → "yz"
队列:queue<{字符串,步数}>,vis集合记录已经出现过的串。
允许最大步数:10。
初始状态
队列:[ ("abcd", 0) ]
vis = { "abcd" }
取出队首:str="abcd", stp=0
stp=0 < 10,可以扩展。
遍历全部3条规则:
- 规则R1:
abc → xu
ori="abc",长度3。
枚举i:i +3 ≤4,i可取0,1
- i=0:
substr(0,3) = "abc"匹配成功
新串:str[0,0] + "xu" + str[3:]→"" + "xu" + "d"→"xud"
"xud" != "xyz",不在vis。加入vis,入队("xud",1) - i=1:
substr(1,3)="bcd"≠abc,不匹配。
-
规则R2:
ud → y,ori长度2abcd中没有子串ud,无新串。 -
规则R3:
y → yzabcd没有y,无新串。
当前队列:
[ ("xud", 1) ]
取出队首:str="xud", stp=1
stp=1 <10。
遍历3条规则:
- R1
abc→xu:"xud"没有abc。 - R2
ud→y,ori长度2
i遍历:i+2 ≤3 → i=0,1
- i=1:
substr(1,2) = "ud"匹配成功
新串:"x" + "y" + ""→"xy"
不等于目标xyz,未访问,入队("xy",2),vis加入"xy"
- R3
y→yz:此时"xud"没有字符y,不产生新串。
当前队列:
[ ("xy", 2) ]
取出队首:str="xy", stp=2
stp=2<10。
遍历规则:
- R1:不匹配。
- R2:
ud不存在。 - R3:
y → yz,ori长度1。
i遍历:i+1 ≤2,i=0,1
- i=1:
substr(1,1) = "y"匹配。
新串:"x" + "yz"→"xyz"。
✅新串"xyz"等于目标字符串!
返回 stp+1 = 2+1 = 3。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
struct Node{
string s;
int step;
};
vector<pair<string,string>> rule;
unordered_set<string> vis;
int bfs(string start,string target){
queue<Node> q;
q.push({start,0});
vis.insert(start);
while(!q.empty()){
Node cur = q.front();
q.pop();
string str = cur.s;
int stp = cur.step;
if(stp >=10) continue; //最多允许10步,不再扩展
//遍历每一条变换规则
for(auto &r : rule){
string ori = r.first;
string rep = r.second;
int lenOri = ori.size();
//枚举所有可以替换的下标位置i
for(int i=0;i+lenOri <= str.size();i++){
if(str.substr(i, lenOri) == ori){
//i位置替换,生成新串
string ns = str.substr(0,i) + rep + str.substr(i+lenOri);
if(ns == target){
return stp+1;
}
if(!vis.count(ns)){
vis.insert(ns);
q.push({ns, stp+1});
}
}
}
}
}
return -1;
}
void solve(){
string A,B;
cin>>A>>B;
string x,y;
while(cin>>x>>y){
rule.emplace_back(x,y);
}
int ans = bfs(A,B);
if(ans == -1){
cout<<"NO ANSWER!"<<endl;
}else{
cout<<ans<<endl;
}
}
signed main(){
ios::sync_with_stdio(0);cin.tie(0);
solve();
return 0;
}
P1021 [NOIP 1999 提高组 邮票面值设计(疑似错题) - 洛谷](https://www.luogu.com.cn/problem/P1021)
P1021 邮票面值设计
题目大意
给定 N N N:最多可以贴 N N N 张邮票;
给定 K K K:一共要设计 K K K 种不同的邮票面值。(满足 N + K ≤ 15 N+K \le 15 N+K≤15)
邮票数量无限供应。要求选出 K K K 个面值,使得 1 ∼ M A X 1 \sim MAX 1∼MAX 的每一个邮资都可以用不超过 N N N 张邮票凑出来 。求最大的 M A X MAX MAX,同时输出选出来的面值(从小到大)。
样例:输入3 2
(N=3)最多贴3张,选2种面值1 3。
(1,2,3,4,5,6,7) 全部都可以用不超过3张凑出来;8无法凑出。得到 (MAX=7)。
注意:面值必须包含1。如果没有面值1,数字1就凑不出来,直接MAX=0。
核心思路
算法组合:**DFS搜索枚举面值 +完全背包DP计算当前方案的连续最大邮资**
1.DFS部分
- 搜索依次确定每一个邮票面值,面值严格递增,避免重复组合。
- 剪枝:假设前面已经选出的面值可以连续凑到 m x mx mx,那么下一个新面值的取值范围:
at-1+1 \le nextVal \le (mx+1)
如果新面值 >(mx+1),那么数字 (mx+1) 永远凑不出来,直接剪去这一分支。
- 递归终止条件:已经选够 K K K 种面值。调用DP算出该方案的连续最大邮资,更新全局最优解。
2.DP(完全背包)
定义 (fi):凑邮资 i i i 需要的最少邮票张数。
- 初始化:(fi)=i(全部用面值1去凑)
- 完全背包:对每一个已经选好的邮票面值,更新 f j = min ( f j , f j − v a l + 1 ) fj=\min(fj,\ fj-val+1) fj=min(fj, fj−val+1)
- 从1开始往后遍历,找到第一个满足 (fi)>N 的下标,那么 i − 1 \boldsymbol{i-1} i−1 就是这套面值能连续到达的最大邮资MAX。
关键点
- DFS枚举所有合法面值组合,DP用来评估这套组合好不好。
- 剪枝
nextVal ≤ \(mx+1\)是本题灵魂,大幅减少搜索量。 - 求最少张数,不是求能否凑出;超过N张就算凑不出来。
- 不需要回溯数组:下一轮循环直接覆盖
a[t]的值。
复杂度说明
N + K ≤ 15 N+K\le15 N+K≤15,加上强力剪枝,搜索量很小,可以直接暴力DFS。
算法流程总结
dfs(t, mx):选第t个面值;mx是前面t-1个面值能连续到的最大邮资。- 循环枚举下一个面值,范围at-1+1,mx+1
- 选定面值,调用dp函数得到新的连续上限
new_mx。 - 递归
dfs(t+1, new_mx)。 - 选满K个面值,对比更新全局最优解。
样例模拟 n=3,k=2
n:最多贴3张邮票;k:选2种面值
全局:
a[]当前搜索面值,ans[]最优解,maxn最大连续邮资初始:
a[0]=0,调用dfs(1, 0)
第一层 dfs( t=1 , mx=0 )
t=1:选第1个面值;mx=0:前面0个面值,连续最大为0
循环条件:
cpp
i >= a[t-1]+1 = a[0]+1 = 1
i <= mx+1 = 0+1 =1
i只能等于1
a[1]=1- 调用
dp(t=1, mx=0),现在已选面值:{1}
dp(1,0)执行过程
t=1,a[t]=a[1]=1,上限:a[t]*n =1*3=3
f数组大小到3,初始化:f[0]=0,f[1]=1,f[2]=2,f[3]=3
完全背包:只处理面值a[1]=1
j从1~3
f[j] = min(f[j], f[j‑1]+1)
处理完后:
f[0]=0,f[1]=1,f[2]=2,f[3]=3
遍历找第一个f[i]>3:
i=1 f=1;i=2 f=2;i=3 f=3;全部≤3。循环结束返回上限3。
✅ dp返回 x=3
调用 dfs(t+1=2 , x=3)
第二层 dfs( t=2 , mx=3 )
t=2:选第2个面值;mx=3:前面
{1}可以连续凑到3循环条件:
cpp
i >= a[t‑1]+1 = a[1]+1 = 2
i <= mx+1 =3+1 =4
i依次取:2,3,4
① i=2,a[2]=2
调用dp(t=2,mx=3),当前面值集合{1,2}
t=2,a[t]=2;上限a[t]*n =2*3=6
初始化f[i]=i:f[0]=0,f1=1,f2=2,f3=3,f4=4,f5=5,f6=6
完全背包:
-先处理面值1:f[0~6]最少张数:0,1,2,3,4,5,6
-再处理面值2:
j=2: min(2,f[0]+1)=1
j=3: min(3,f[1]+1)=2
j=4: min(4,f[2]+1)=2
j=5: min(5,f[3]+1)=3
j=6: min(6,f[4]+1)=3
最终f数组:
| i | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| fi | 0 | 1 | 1 | 2 | 2 | 3 | 3 |
逐个检查:
1:1,2:1,3:2,4:2,5:3,6:3;i=7超出循环上限。
dp返回 x=6。
现在t=2 == k,进入下一层dfs(3,6)。
dfs(t=3,mx=6)
t==k+1 (3==2+1)成立。
mx=6 > maxn(初始0)。
更新maxn=6,ans[1]=1 ans[2]=2。return回到上一层。
② i=3,a[2]=3
调用dp(t=2,mx=3),面值集合{1,3}
t=2,a[t]=3;上限a[t]*n =3*3=9
初始化f[i]=i。
完全背包:
-
面值1处理完:
f[i]=i -
处理面值3:
j=3: min(3,f[0]+1)=1
j=4: min(4,f[1]+1)=2
j=5: min(5,f[2]+1)=3
j=6: min(6,f[3]+1)=2
j=7: min(7,f[4]+1)=3
j=8: min(8,f[5]+1)=4
j=9: min(9,f[6]+1)=3
f数组关键项:
| i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|
| fi | 1 | 2 | 1 | 2 | 3 | 2 | 3 | 4 |
开始遍历:
i=1~7全部≤3;i=8时f[8]=4 > 3。
✅ 第一个超3的i=8;dp返回x = i‑1 =7。
调用dfs(3,7)
t ==k+1,mx=7 > maxn(旧值6)
更新:maxn=7,ans[1]=1 ans[2]=3。return。
③ i=4,a[2]=4
调用dp(t=2,mx=3),面值集合{1,4}
上限4*3=12
f数组:
1:1,2:2,3:3,4:1,5:2,6:3,7:4。
i=7,f7=4>3,dp返回x=6。
调用dfs(3,6),mx=6,比当前maxn=7小,不更新最优解。return。
for循环全部i处理完毕,dfs(2,3)返回。
dfs(1,0)返回,搜索全部结束。
变量 含义
n 最多允许贴 n 张邮票
k 一共要选 k 种面值
a\[\] dfs 搜索过程中,当前正在尝试的面值数组
ans\[\] 保存最优的那一组面值
maxn 全局最大连续邮资 MAX
dp(t,mx) 传入已经选好t个面值,返回这套面值可以连续凑出的最大邮资
输出结果
1 3
MAX=7
#include<bits/stdc++.h>
using namespace std;
int n,k,ans[20],a[20],maxn;
// dp(t,mx): 当前已经选了 t 个面值,计算这套面值能连续到的最大邮资
int dp(int t,int mx)
{
int f[10000];
//初始化:f[i]表示凑i最少需要多少张邮票,初始赋值i(最坏全部用1)
for(int i=0;i<=a[t]*n;i++)
f[i]=i;
//完全背包
for(int i=1;i<=t;i++)
{
for(int j=a[i];j<=a[t]*n;j++)
f[j]=min(f[j],f[j-a[i]]+1);
}
//找第一个需要邮票数>n的位置
for(int i=1;i<=a[t]*n;i++)
{
if(f[i]>n)
return i-1;
}
return a[t]*n;
}
// dfs(t,mx): t表示当前要选第t个面值;mx:前面t?1个面值能够连续到的最大邮资
void dfs(int t,int mx)
{
//递归终止:已经选够k个面值
if(t==k+1)
{
if(mx>maxn)
{
maxn=mx;
for(int i=1;i<=k;i++)
ans[i]=a[i];
}
return;
}
/**
* 剪枝循环:
* a[t?1]+1:面值严格递增,比上一个面值大
* mx+1:新增面值不能超过mx+1,否则mx+1这个数字凑不出来
for(int i=a[t-1]+1;i<=mx+1;i++)
{
a[t]=i;
int x=dp(t,mx);
dfs(t+1,x);
}
}
int main()
{
cin>>n>>k;
a[0]=0;
dfs(1,0);
for(int i=1;i<=k;i++)
cout<<ans[i]<<" ";
cout<<endl;
cout<<"MAX="<<maxn<<endl;
return 0;
}