【洛谷P4950】完美数字
状压数位 DP
题目大意:
给定数字集合 $S$ 和数字集合 $T$。问 $[l,r]$ 中所有满足以下条件的数 $x$ 之和。
- 数字集合中的所有数字都出现在 $x$ 的数位中。
- 不存在一个数位是数字集合 $T$ 中的数字。
保证 $S$ 与 $T$ 无交集且元素不会重复。
题解:
思路比较简单,就是把当前已经出现的 $S$ 中的元素状压起来,然后进行数位 dp。
这道题目采用预处理后面 $w$ 位任意填的情况,然后从高位往低位考虑,枚举每一位的值,然后后面的位直接使用预处理的答案计算的方法比较简单,且效率高。
令 $f[s][w]$ 表示 $w$ 位数,$S$ 中的元素出现的情况为 $s$ 的符合条件的数的和(这里考虑前导零),$g[s][w]$ 表示上述条件下的数的个数。
统计答案的时候,设当前的数有 $W$ 位,那么我们首先处理不足 $W$ 位的数的贡献。枚举位数,枚举最高位的数,然后通过 $f$ 和 $g$ 就可以计算答案。
接下来考虑 $W$ 位的数,从高位往低位枚举,枚举到第 $i$ 位的时候,更高位已经和最大值相等。则后面的数的所有情况,也可以用 $f$ 和 $g$ 的答案求得。
时间复杂度上界为 $O(2^{|S|}\times 100\times t)$。由于实际运行效率和 $l,r$ 的数位上的值有关,在测试数据上跑得飞快。实际上是有方法卡到比较慢的。但这仍是正确的复杂度。
Code:
#include<iostream>
#include<cstring>
using namespace std;
typedef long long LL;
int tt,l,r,S[11],T[11],tot,C;
const int W[]={1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000};
LL f[1024][10],g[1024][10];
void work(int w){
if(w==0){
for(int i=0;i<10;++i)
if(!T[i]){
if(S[i]!=-1)
++g[1<<S[i]][0],f[1<<S[i]][0]+=i;else
++g[0][0],f[0][0]+=i;
}
}else{
work(w-1);
for(int i=0;i<=C;++i)
for(int s=0;s<10;++s)
if(!T[s]){
int zt=i;
if(S[s]!=-1)zt|=1<<S[s];
g[zt][w]+=g[i][w-1];
f[zt][w]+=g[i][w-1]*W[w]*s+f[i][w-1];
}
}
}
inline LL ask(int n){
int w=0;
for(int i=0;i<10;++i)
if(n>=W[i])w=i;
LL ret=0;
for(int i=w-1;i>=0;--i){
for(int s=1;s<10;++s)
if(!T[s]){
ret+=s*W[i]*(i?g[C][i-1]:(tot==0?1:0))+(i?f[C][i-1]:0);
if(S[s]!=-1)ret+=s*W[i]*(i?g[C^(1<<S[s])][i-1]:((1<<S[s])==C))+(i?f[C^(1<<S[s])][i-1]:0);
}
}
int sm=0,ss=0;
for(int i=w;i>=0;--i){
int wg=n/W[i]%10;
for(int s=(i==w)?1:0;s<wg;++s)if(!T[s]){
int zt=ss,sum=sm+W[i]*s;if(S[s]!=-1)zt|=1<<S[s];
if(i==0)ret+=(zt==C)?sum:0;else
for(int ww=0;ww<=C;++ww)if((zt|ww)==C){
ret+=sum*g[ww][i-1]+f[ww][i-1];
}
}
sm+=wg*W[i];
if(T[wg])return ret;
if(S[wg]!=-1)ss|=1<<S[wg];
}
if(ss==C)ret+=sm;
return ret;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
for(cin>>tt;tt--;){
memset(S,-1,sizeof S);
memset(T,tot=0,sizeof T);
memset(f,0,sizeof f);
memset(g,0,sizeof g);
cin>>l>>r;
int x,y;
for(cin>>x;x--;){
cin>>y;
S[y]=tot++;
}
C=(1<<tot)-1;
for(cin>>x;x--;){
cin>>y;
T[y]=1;
}
work(9);
cout<<ask(r)-ask(l-1)<<'\n';
}
return 0;
}