题解 P2518 【[HAOI2010]计数】
数位DP(记忆化BFS)
在文章的开始先%%教会我的@ywy_c_asm 大佬和 shadowice1984
ywy_c_asm的题解
按照数位DP的常规套路,dfs枚举每一个小于等于自己的数,再判断该数是否与原数是否有着除0以外的相同数码
那么问题来了,怎么判断该数是否与原数是否有着除0以外的相同数码呢?一种直观的想法是开一个数组记录原数中除0以外每个数码出现的次数,然而如果这么做的话时间和空间都要炸。
再想一想,如果原问题为一个不重复的集合(即每个数出现次数都是1或0),我们可以怎么做呢?一种解决方法就是开一个unsigned long long se,se|(1<<i-1)就表示将i数加入集合中
然而这道题一个数可以出现多次!这时候我们就要用到一个技巧,把unsigned long long强制转化为8个char(一个unsigned long long8个字节,char一个字节),就是这行代码
(((char *)&a)[x]
它表示把unsigned long long a转化为char后的a[x],其中x必须小于8
一个unsigned long long只能存8个,显然是不够的,所以我们需要把两个拼接起来,而hash的key值就有这两个unsigned long long得到
然后我们就可以愉快地搜索了!我用的是宽搜,开两个队列,一个存了上一层的节点,一个存了这一层的节点,使用滚动数组的技巧节约空间,就可以AC这道题了!一个队列应该也是可以过的,有兴趣的同学可以尝试一下
#include<bits/stdc++.h>
using namespace std;
#define go(i,a,b) for(int i=(a);i<=(b);++(i))
#define com(i,a,b) for(int i=(a);i>=(b);--(i))
#define mem(a,b) memset((a),(b),sizeof(a))
typedef long long ll;
typedef unsigned long long ull;
char s[55];
const int mod=1000003;
int n,num[55];
#define inl inline
struct point{
ull a, b;
point(){a=b=0;}
inline int get(int x) {
if (x < 8) return (((char *)&a)[x]);
return (((char *)&b)[x-8]);
}
inline void set(int x, int num) {
if (x < 8) ((char *)&a)[x] = num;
else ((char *)&b)[x - 8] = num;
}
inline int get_hash() {
ull ha = a;
ha %= mod;
ha <<= 32;
ha |= (b % mod);
return (ha % mod);
}
friend bool operator == (point &a,point &b){
return a.a==b.a&&a.b==b.b;
}
};
struct edge{
point dat;
ll dp;int nxt;
};
struct hash{
edge e[mod+5];
int cnt=0,head[mod+5];
inl void clear(){mem(head,-1),cnt=0;}
inl ll query(int u,point x){
for(int i=head[u];i+1;i=e[i].nxt){
if(e[i].dat==x) return e[i].dp;
}
return 0;
}
inl bool insert(point x,ll dp){
int u=x.get_hash();
for(int i=head[u];i+1;i=e[i].nxt){
if(e[i].dat==x){
e[i].dp+=dp;
return 0;
}
}
e[cnt]=(edge){x,dp,head[x.get_hash()]};
head[u]=cnt++;
return 1;
}
};
point q[2][mod+5];
hash h[2];
int cnt[2];
#define last i&1
#define now ((i&1)^1)
int main()
{
scanf("%s",s+1);
n=strlen(s+1);
go(i,1,n) num[i]=s[n-i+1]-'0';
point st;
go(i,1,n){
if(num[i]){
st.set(num[i],st.get(num[i])+1);
}
}
st.set(10,1);//limit=1
cnt[n&1]=1;q[n&1][0]=st;//根节点
h[n&1].clear();
h[n&1].insert(st,1);
com(i,n,1){
cnt[now]=0;
h[now].clear();
go(j,0,cnt[last]-1){
point fro=q[last][j];
ll dp=h[last].query(fro.get_hash(),fro);
go(k,0,fro.get(10)?num[i]:9){
point x=fro;
if(k&&!x.get(k)) continue;
if(k) x.set(k,x.get(k)-1);
if(x.get(10)&&k<num[i]) x.set(10,0);
if(h[now].insert(x,dp)){
q[now][cnt[now]]=x;cnt[now]++;
}
}
}
}
printf("%lld",h[0].query(0,point()));
return 0;
}