湖北省赛2022H.Hamster and Multiplication

真是没想到,原来数位dp也能滚动数组优化
题目链接
题意很简单,定义
f ( x ) = { x , x < 10 f ( Π i x i ) , x ≥ 10 f(x)= \left\{\begin{matrix} x,x<10 \\ f(\Pi_ix_i),x≥10 \end{matrix}\right. f(x)={x,x<10f(Πixi),x10
Σ i = 1 n f ( i ) \Sigma_{i=1}^nf(i) Σi=1nf(i)
先说下我的“错误做法”(思路有点小问题,但其实可以改对)
f [ p o s ] [ s t ] [ f l ] [ f l 2 ] f[pos][st][fl][fl2] f[pos][st][fl][fl2]代表处理到 p o s pos pos位,
当前 2...9 2...9 2...9的数量的状态为 s t st st(注意到有贡献的数肯定不存在某位是0,且1不影响乘积,其实这里就可以看出我这个定义可以改改),
是否贴着上界 ( f l ) (fl) (fl)
之前是否全是 0 ( f l 2 ) 0(fl2) 0(fl2)(最开始可以填0是因为位数不一定要和n相同)
s t st st的状态隔板法计算一下就是 C 26 8 = 1 , 562 , 275 C^8_{26}=1,562,275 C268=1,562,275(开始算错了,以为就5w个状态,不然肯定不会这么莽)
由于不知道怎么想的,认为只能从某个状态更新之后的状态,而不能每次用之前状态计算当前状态,所以dfs就不成立了(某个节点更新多次后才是最优的,根本不知道什么时候才能拿来更新别的点),于是我就采用了bfs,在实现时发现会爆内存,采用了滚动数组优化
以下是用当前思路写出来的“错误”代码,极限数据cf需要1.7s

#include
using namespace std;
const int N=500002;
typedef long long ll;
unordered_map<ll,int>mp;
ll f[2][N][2][2],n,ans,v,pw[10];
bool vis[2][N][2][2];
int i,tot[N],cnt,lim,d[20];
struct node{
	int pos;
	ll v;
	bool fl,fl2;
};
inline int calc(ll v){//计算v状态的答案
	ll x=1;
	for (int i=7;~i;v%=pw[i],i--)
		for (int j=0;j<v/pw[i];j++) x*=i+2;
	while (x>=10){
		ll y=1;
		while (x){
			y*=x%10;
			x/=10;
		}
		x=y;
	}
	return x;
}
//pre函数预处理每个状态的映射
inline void pre(ll v,int x,int sum){//x代表当前处理x+2,v[0...7]代表[2...9] 
	if (!mp.count(v)) mp[v]=++cnt,tot[cnt]=calc(v);
	if (x==8 || sum==lim) return;
	pre(v,x+1,sum);
	if (x==3 && (v%pw[1] || v%pw[3]/pw[2])) return;//处理5时不能出现2,4,剪枝
	if (x==4 && v%pw[4]/pw[3]) return;//处理6之前不能出现5
	if (x==6 && v%pw[4]/pw[3]) return;//处理8之前不能出现5
	v+=pw[x];
	pre(v,x,sum+1);
	v-=pw[x];
}
void doit(ll n){
	while (n){
		d[++lim]=n%10;
		n/=10;
	}
}
inline void bfs(int pos,ll v,bool fl,bool fl2){
	queue<node>q;
	q.push({pos,v,fl,fl2});
	int x1=pos&1,x2=x1^1;
	int las=pos;
	while (!q.empty()){
		pos=q.front().pos;
		v=q.front().v;
		fl=q.front().fl;
		fl2=q.front().fl2;
		q.pop();
		if (!pos){
			ans+=f[x2][mp[v]][fl][fl2]*tot[mp[v]];
			continue;
		}
		if (pos==las-1){
			x1^=1,x2^=1;
			memset(f[x2],0,sizeof(f[x2]));
			memset(vis[x2],0,sizeof(vis[x2]));
			las=pos;
		}
		ll t=f[x1][mp[v]][fl][fl2];
		int up=(fl?d[pos]:9);
		for (int i=!fl2;i<=up;i++){
			if (i==5 && (v%pw[1] || v%pw[3]/pw[2])) continue;
			if ((i==6 || i==8) && v%pw[4]/pw[3]) continue;
			if (i>1) v+=pw[i-2];
			int to=mp[v];
			f[x2][to][fl&(i==up)][fl2&(i==0)]+=t;
			if (!vis[x2][to][fl&(i==up)][fl2&(i==0)]){
				q.push({pos-1,v,fl&(i==up),fl2&(i==0)});
				vis[x2][to][fl&(i==up)][fl2&(i==0)]=1;
			}
			if (i>1) v-=pw[i-2];
		}
	}
}
int main(){
	scanf("%lld",&n);
	pw[0]=1;
	for (i=1;i<=7;i++) pw[i]=pw[i-1]*18;
	doit(n);
	pre(0,0,0);
	f[lim&1][1][1][1]=1;
	bfs(lim,v,1,1);
	printf("%lld",ans-1);
}

其实在写剪枝的时候,我就发现了另外一个性质:每个数可以用它的因数表示
我当时就试了一个9,拆成两个3,结果爆了,以为是错误的,后来才知道是我这么做的话,3可能有36个,而我当作18在做
其实由这个就可以发现,没必要存储完整的状态,存储所有数位的乘积即可(vp的时候脑抽没想到),状态只有36100个,大大减小了复杂度,代码也更简单:

#include
using namespace std;
const int N=50002;
typedef long long ll;
unordered_map<ll,int>mp;
ll f[2][N][2][2],n,ans,v;
bool vis[2][N][2][2];
int i,tot[N],cnt,lim,d[20];
struct node{
	int pos;
	ll v;
	bool fl,fl2;
};
inline int calc(ll v){
	while (v>=10){
		if (mp.count(v)) return tot[mp[v]];
		ll x=1;
		while (v){
			x*=v%10;
			v/=10;
		}
		v=x;
	}
	return v;
}
inline void bfs(int pos,bool fl,bool fl2){//fl2:之前全0
	queue<node>q;
	ll v;
	q.push({pos,1,fl,fl2});
	int x1=pos&1,x2=x1^1;
	int las=pos; 
	while (!q.empty()){
		pos=q.front().pos;
		v=q.front().v;
		fl=q.front().fl;
		fl2=q.front().fl2;
		q.pop();
		if (!pos){
			ans+=f[x2][mp[v]][fl][fl2]*tot[mp[v]];
			continue;
		}
		if (pos==las-1){
			x1^=1,x2^=1;
			memset(f[x2],0,sizeof(f[x2]));
			memset(vis[x2],0,sizeof(vis[x2]));
			las=pos;
		}
		ll t=f[x1][mp[v]][fl][fl2];
		int up=(fl?d[pos]:9);
		for (int i=!fl2;i<=up;i++){
			if (i) v*=i;
			if (!mp.count(v)) tot[++cnt]=calc(v),mp[v]=cnt;
			int to=mp[v];
			f[x2][to][fl&(i==up)][fl2&(i==0)]+=t;
			if (!vis[x2][to][fl&(i==up)][fl2&(i==0)]){
				q.push({pos-1,v,fl&(i==up),fl2&(i==0)});
				vis[x2][to][fl&(i==up)][fl2&(i==0)]=1;
			}
			if (i) v/=i;
		}
	}
}
int main(){
	scanf("%lld",&n);
	while (n){
		d[++lim]=n%10;
		n/=10;
	}
	mp[1]=++cnt,tot[1]=1;
	f[lim&1][1][1][1]=1;
	bfs(lim,1,1);
	printf("%lld",ans-1);
}

我过了以后甚至还想直接状态变成 f ( x ) f(x) f(x)的值,但这样的话两个无关的状态也能转化了,故不可

你可能感兴趣的:(数位dp,杂,c++,算法)