P1037 [NOIP2002 普及组] 产生数

一.题目概括

一个整数 n 和 k 个数位变化规则。求出经过任意次的变换(0 次或多次),能产生出多少个不同整数。(变化规则示例:2 4表示数字“2”可以变化为数字“4”)

输出方案数(注意数字本身保持不变也算一种方案)

二.关键点

1.纯暴力(STLset)

设结构体数组f(或使用pair)存储,深搜一遍,将变化字符串存入集合st中,最后输出st中字符串数字(由于集合可以自动去重,所以可以统计不重合方案数)

这种方法不无优点,比如简单易懂易上手(本人一开始也是使用这种方法),如果数据范围适合,也不是不可以使用

但是看数据限制:n<=10^30,所以只能得到60分

2.高精度+乘法原理
(1).乘法原理

如果实现一个目标必须经过n个步骤,第k步又可以有m_{k}种不同方式来实现(k=1,2,....,n),那么实现这个目标总共有N=\prod_{i-1}^{n}m_{k}=m_{1}m_{2}...m_{n}种方法。

--引自百度

简单来说,以样例

234 2
2 5
3 6

为例:字符串s(即“234”)的变化方案为

“2”的变化方案数*“3”的变化方案数*“4”的变化方案数

得到表格

变化方案表
21+1=2(不变+2变5)
31+1=2(不变+3变6)
41=1(不变)
234(答案)2*2*1=4(变化2*变化3*变化4)

但是随之得来一个问题:

如果溢出,怎么办?

(2) 高精度

已知int类的最大值为2147483647,long long类的最大值为9223372036854775807,unsigned long long类的最大值为18446744073709551615,_int128类的最大值为2^(128-1)-1

但是我们知道乘法的增长是恐怖的,按照规定的数据范围,无法保证是否可以用以上常用的数据存储范围来存储答案

所以可以引入高精度算法(理论上,如果内存足够大,高精度的表示上限可以达到正无穷)

高精度的精髓就是用数组来表示数,数组第i位表示数字第i位(但是为了方便计算,通常将数组表示反转),这里只介绍高精*低精的代码

第一步:将数组每一位乘以低精度数字x

第二步遍历数组,处理进位

第三步:处理数组长度

第四步逆序输出

以下代码建议熟练背诵

//高精乘低精板子
void HPNmul(int x[],int y){//x表示高精度数组,y表示低精度数
	for(int i=1;i<=x[0];i++)x[i]*=y;
	for(int i=1;i<=x[0];i++){
		if(x[i]>=10){
			x[i+1]+=x[i]/10;
			x[i]%=10;
			if(i==x[0])x[0]++;
		}
	}
}

三.代码

1.暴力代码(60分)
//也求助一下 能不能继续优化到100
#include <iostream>
#include <string>
#include <set>
using namespace std;
struct Node{
	char x,y;
	Node(){}
	Node(char a,char b):x(a),y(b){}
}f[20];
string s;
int n,ct=1;
char u,v;
set<string> st;
void dfs(string s){
	for(int i=1;i<=n;i++){
		int pos=s.find(f[i].x);
		if(pos!=string::npos){
			string s1=s;
			s1[pos]=f[i].y;
			if(!st.count(s1)){
				st.insert(s1);
				ct++; 
				dfs(s1);
			}
		}
	}
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0); 
	cin>>s>>n;
	for(int i=1;i<=n;i++){
		cin>>u>>v;
		f[i]=Node(u,v);
	}
	st.insert(s);
	dfs(s);
	cout<<ct;
}

时间:

2.AC代码(高精度+乘法原理)
#include <iostream>
#include <cstring>
#define C vis[c]
using namespace std;
struct Node{
	char x,y;
	Node(){}
	Node(char a,char b):x(a),y(b){}
}f[20];
string s;
int n,u,v,ct[105],tct;
bool vis['9'+5];
void dfs(char c){
	if(C)return;
	tct++;
	C=true;
	for(int i=1;i<=n;i++){
		if(f[i].x==c)dfs(f[i].y);
	}
}
void HPNmul(int x[],int y){
	for(int i=1;i<=x[0];i++)x[i]*=y;
	for(int i=1;i<=x[0];i++){
		if(x[i]>=10){
			x[i+1]+=x[i]/10;
			x[i]%=10;
			if(i==x[0])x[0]++;
		}
	}
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>s>>n;
	ct[0]=1,ct[1]=1;
	for(int i=1;i<=n;i++)cin>>f[i].x>>f[i].y;
	for(int i=0;i<s.length();i++){
		tct=0;
		memset(vis,false,sizeof(vis));
		dfs(s[i]);
		HPNmul(ct,tct);
	}
	for(int i=ct[0];i>=1;i--)cout<<ct[i];
}

四.直达链接

https://www.luogu.com.cn/problem/P1037

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值