真是没想到,原来数位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),x≥10
求 Σ 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)的值,但这样的话两个无关的状态也能转化了,故不可