P2518 [HAOI2010] 计数 题解
思路
一眼数位 DP。
很显然,我们不会想到直接写上 DP,而是写更好写的记忆化搜索。我们定义
#include<bits/stdc++.h>
using namespace std;
short len,a[51];
string s;
map<short,map<short,map<short,map<short,map<short,map<short,map<short,map<short,map<short,map<short,map<short,long long>>>>>>>>>>> F;
short cnt[10];//存每个数字需要出现的次数
inline long long dfs(short now_where,short if_le,short if_z,short z,short o,short t,short tr,short f,short fi,short s,short se,short e,short ni){
if(!now_where) return (o==cnt[1]&&t==cnt[2]&&tr==cnt[3]&&f==cnt[4]&&fi==cnt[5]&&s==cnt[6]&&se==cnt[7]&&e==cnt[8]&&ni==cnt[9])?1:0;
else if(!if_le&&!if_z&&F[now_where][z][o][t][tr][f][fi][s][se][e].count(ni)) return F[now_where][z][o][t][tr][f][fi][s][se][e][ni];
long long ans=0,up=if_le?a[now_where]:9;
for(register short i=0;i<=up;++i){
if(if_z&&i==0) ans+=dfs(now_where-1,if_le&&i==up,1,z,o,t,tr,f,fi,s,se,e,ni);
else if(i==0&&z +1<=cnt[0]) ans+=dfs(now_where-1,if_le&&i==up,0,z+1,o,t,tr,f,fi,s,se,e,ni);
else if(i==1&&o +1<=cnt[1]) ans+=dfs(now_where-1,if_le&&i==up,0,z,o+1,t,tr,f,fi,s,se,e,ni);
else if(i==2&&t +1<=cnt[2]) ans+=dfs(now_where-1,if_le&&i==up,0,z,o,t+1,tr,f,fi,s,se,e,ni);
else if(i==3&&tr+1<=cnt[3]) ans+=dfs(now_where-1,if_le&&i==up,0,z,o,t,tr+1,f,fi,s,se,e,ni);
else if(i==4&&f +1<=cnt[4]) ans+=dfs(now_where-1,if_le&&i==up,0,z,o,t,tr,f+1,fi,s,se,e,ni);
else if(i==5&&fi+1<=cnt[5]) ans+=dfs(now_where-1,if_le&&i==up,0,z,o,t,tr,f,fi+1,s,se,e,ni);
else if(i==6&&s +1<=cnt[6]) ans+=dfs(now_where-1,if_le&&i==up,0,z,o,t,tr,f,fi,s+1,se,e,ni);
else if(i==7&&se+1<=cnt[7]) ans+=dfs(now_where-1,if_le&&i==up,0,z,o,t,tr,f,fi,s,se+1,e,ni);
else if(i==8&&e +1<=cnt[8]) ans+=dfs(now_where-1,if_le&&i==up,0,z,o,t,tr,f,fi,s,se,e+1,ni);
else if(i==9&&ni+1<=cnt[9]) ans+=dfs(now_where-1,if_le&&i==up,0,z,o,t,tr,f,fi,s,se,e,ni+1);
}
if(!if_le&&!if_z) F[now_where][z][o][t][tr][f][fi][s][se][e][ni]=ans;
return ans;
}
inline void solve(){
cin>>s;
for(register short i=s.size()-1;i>=0;--i) a[++len]=s[i]-'0',++cnt[s[i]-'0'];
printf("%lld",dfs(len,1,1,0,0,0,0,0,0,0,0,0,0)-1);
}
int main(){
solve();
return 0;
}
考虑优化掉
如何将
我们写一个函数,便于计算某些值用这种方法转化成字符串,在每一次搜索完成后,只需要判断该情况与目标字符串是否相等即可。
注:这里有一个剪枝,就是在一个数字出现的次数大于目标出现次数的时候,我们是可以放弃这种情况的搜索的。
代码
#include<bits/stdc++.h>
using namespace std;
short len,a[51],cnt[10];
string s,all;
map<short,map<string,long long>> F;
inline string get(short o,short t,short tr,short f,short fi,short s,short se,short e,short ni){
string nowk="";short w;
w=o ;while(w) nowk+=(w%10+'0'),w/=10;nowk+=',';//加区分的字符
w=t ;while(w) nowk+=(w%10+'0'),w/=10;nowk+=',';
w=tr;while(w) nowk+=(w%10+'0'),w/=10;nowk+=',';
w=f ;while(w) nowk+=(w%10+'0'),w/=10;nowk+=',';
w=fi;while(w) nowk+=(w%10+'0'),w/=10;nowk+=',';
w=s ;while(w) nowk+=(w%10+'0'),w/=10;nowk+=',';
w=se;while(w) nowk+=(w%10+'0'),w/=10;nowk+=',';
w=e ;while(w) nowk+=(w%10+'0'),w/=10;nowk+=',';
w=ni;while(w) nowk+=(w%10+'0'),w/=10;nowk+=',';
return nowk;
}
inline long long dfs(short now_where,bool if_le,bool if_z,short z,short o,short t,short tr,short f,short fi,short s,short se,short e,short ni,string now){
if(!now_where) return now==all;
if(!if_le&&!if_z&&F[now_where].count(now)) return F[now_where][now];
long long ans=0,up=if_le?a[now_where]:9;
for(register short i=0;i<=up;++i){
if(if_z&&i==0)ans+=dfs(now_where-1,if_le&&i==up,1,0,0,0,0,0,0,0,0,0,0,"");
else if(i==0) ans+=dfs(now_where-1,if_le&&i==up,0,z+1,o,t,tr,f,fi,s,se,e,ni,now);
else if(i==1&&o +1<=cnt[1]) ans+=dfs(now_where-1,if_le&&i==up,0,z,o+1,t,tr,f,fi,s,se,e,ni,get(o+1,t,tr,f,fi,s,se,e,ni));
else if(i==2&&t +1<=cnt[2]) ans+=dfs(now_where-1,if_le&&i==up,0,z,o,t+1,tr,f,fi,s,se,e,ni,get(o,t+1,tr,f,fi,s,se,e,ni));
else if(i==3&&tr+1<=cnt[3]) ans+=dfs(now_where-1,if_le&&i==up,0,z,o,t,tr+1,f,fi,s,se,e,ni,get(o,t,tr+1,f,fi,s,se,e,ni));
else if(i==4&&f +1<=cnt[4]) ans+=dfs(now_where-1,if_le&&i==up,0,z,o,t,tr,f+1,fi,s,se,e,ni,get(o,t,tr,f+1,fi,s,se,e,ni));
else if(i==5&&fi+1<=cnt[5]) ans+=dfs(now_where-1,if_le&&i==up,0,z,o,t,tr,f,fi+1,s,se,e,ni,get(o,t,tr,f,fi+1,s,se,e,ni));
else if(i==6&&s +1<=cnt[6]) ans+=dfs(now_where-1,if_le&&i==up,0,z,o,t,tr,f,fi,s+1,se,e,ni,get(o,t,tr,f,fi,s+1,se,e,ni));
else if(i==7&&se+1<=cnt[7]) ans+=dfs(now_where-1,if_le&&i==up,0,z,o,t,tr,f,fi,s,se+1,e,ni,get(o,t,tr,f,fi,s,se+1,e,ni));
else if(i==8&&e +1<=cnt[8]) ans+=dfs(now_where-1,if_le&&i==up,0,z,o,t,tr,f,fi,s,se,e+1,ni,get(o,t,tr,f,fi,s,se,e+1,ni));
else if(i==9&&ni+1<=cnt[9]) ans+=dfs(now_where-1,if_le&&i==up,0,z,o,t,tr,f,fi,s,se,e,ni+1,get(o,t,tr,f,fi,s,se,e,ni+1));
}
if(!if_le&&!if_z) F[now_where][now]=ans;
return ans;
}
inline void solve(){
cin>>s;
for(register short i=s.size()-1;i>=0;--i) a[++len]=s[i]-'0',++cnt[s[i]-'0'];
all=get(cnt[1],cnt[2],cnt[3],cnt[4],cnt[5],cnt[6],cnt[7],cnt[8],cnt[9]);
printf("%lld",dfs(len,1,1,0,0,0,0,0,0,0,0,0,0,"")-1);//-1是减去的s这种情况,因为我们求的值s之前的数的个数
}
int main(){
solve();
return 0;
}
注:虽然上面的代码比 @WaterSun 的长一些,但是用时和内存都更优哦。