数位DP AC十道题目以上 成就达成

八月份!三个月!想想就令人兴奋呢

开始写总结啦

貌似简单的数位DP只需要改改模板就可以啦

就按照我的做题顺序开始总结吧

先是学习了一发模板:http://www.cnblogs.com/jffifa/archive/2012/08/17/2644847.html

但是一开始学的不是很深刻,导致后来做题的时候犯了很多错误

hdu 2089

数字中不能出现62和4

一开始设计的状态是f[i][j]表示长度为i且上一个数字为j,然后写了一发过来

其实状态是可以精简的,可以精简成f[i][0/1]表示长度为i上一个数字是否为6

精简后的状态比裸DP快的多,精简后的代码

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std; int L,R;
int f[12][2];//0 无6 1 有6
int Num[12],len=0;
void check(int n){
memset(Num,0,sizeof(Num));len=0;
if(!n){Num[++len]=0;return;}
while(n)Num[++len]=n%10,n/=10;
}
int DFS(int pos,int six,int flag){
if(!pos)return 1;
if(!flag&&f[pos][six]!=-1)return f[pos][six];
int tmp=0,u=flag?Num[pos]:9;
for(int i=0;i<=u;++i){
if(i==4)continue;
if(six&&i==2)continue;
tmp+=DFS(pos-1,i==6,flag&&i==u);
}return flag?tmp:f[pos][six]=tmp;
}
int main(){
while(scanf("%d%d",&L,&R)==2){
if(!L&&!R)break;
check(R);memset(f,-1,sizeof(f));
R=DFS(len,0,1);
check(L-1);memset(f,-1,sizeof(f));
L=DFS(len,0,1);
printf("%d\n",R-L);
}return 0;
}

hdu 3555

数字中不能出现49,跟上道题目做法一样

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std; typedef long long LL;
LL n;
LL f[22][2];//0 不是4 1 是4
int T;
int Num[22],len; void check(LL n){
memset(Num,0,sizeof(Num));len=0;
if(!n){Num[++len]=0;return;}
while(n)Num[++len]=n%10,n/=10;
}
LL DFS(int pos,int four,int flag){
if(!pos)return 1;
if(!flag&&f[pos][four]!=-1)return f[pos][four];
LL tmp=0;
int u=flag?Num[pos]:9;
for(int i=0;i<=u;++i){
if(four&&i==9)continue;
tmp+=DFS(pos-1,i==4,flag&&i==u);
}return flag?tmp:f[pos][four]=tmp;
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%lld",&n);
check(n);memset(f,-1,sizeof(f));
printf("%lld\n",n+1-DFS(len,0,1));
}return 0;
}

hdu 3652

数字中要求有13这个子串同时自身能被13整除

加一维0/1/2表示当前数字和13的匹配长度

再加一维记录余数即可

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std; int n;
int t[13];
int f[13][13][3];
int Num[13],len=0; void check(int n){
memset(Num,0,sizeof(Num));len=0;
if(!n){Num[++len]=0;return;}
while(n)Num[++len]=n%10,n/=10;
}
int DFS(int pos,int mod,int one,int flag){
if(!pos){
if(one==2&&!mod)return 1;
return 0;
}
if(!flag&&f[pos][mod][one]!=-1)return f[pos][mod][one];
int tmp=0,u=flag?Num[pos]:9;
for(int i=0;i<=u;++i){
int now=one;
if(now!=2){
if(now==1&&i==3)now=2;
else if(i==1)now=1;
else now=0;
}
tmp+=DFS(pos-1,(mod+i*t[pos])%13,now,flag&&i==u);
}return flag?tmp:f[pos][mod][one]=tmp;
} int main(){
t[1]=1;
for(int i=2;i<=10;++i)t[i]=t[i-1]*10;
while(scanf("%d",&n)==1){
check(n);memset(f,-1,sizeof(f));
n=DFS(len,0,0,1);
printf("%d\n",n);
}return 0;
}

codeforces #55 D

求能被自己各位非零数字整除的数字

我们发现我们需要记录这个数字%(1-9)的值,如果强行加维会爆炸

但是我们发现对于%p来说,假设我们知道一个数字%(k*p)的值,我们就可以知道其%p的值

那么我们只需要记录这个数字%2520的余数就可以了(LCM(1-9)=2520)

之后我们需要知道这个数字中那些数字出现过,在这里其实我们只需要知道2-9是否出现过就可以了

可以在加一维状态压缩来记录

更好一点的做法是我们发现%2=0和%5=0我们只需要判断最后一位数字就可以了

而去掉2和5之后的LCM=252,这样我们就可以把状态数变成了原来的1/10

但是本人还是写的2520的QAQ

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std; typedef long long LL;
int T;
int Num[22],len=0;
LL f[22][2520][256];
LL t[22];
LL L,R; void check(LL n){
memset(Num,0,sizeof(Num));len=0;
if(!n){Num[++len]=0;return;}
while(n)Num[++len]=n%10,n/=10;
}
LL DFS(int pos,int mod,int S,int flag){
if(!pos){
for(int i=0;i<8;++i){
if(S>>i&1){
if(mod%(i+2))return 0;
}
}return 1;
}
if(!flag&&f[pos][mod][S]!=-1)return f[pos][mod][S];
LL tmp=0;
int u=flag?Num[pos]:9;
for(int i=0;i<=u;++i){
int now=S;
if(i>1)now|=(1<<(i-2));
tmp=tmp+DFS(pos-1,(mod+i*t[pos])%2520,now,flag&&i==u);
}return flag?tmp:f[pos][mod][S]=tmp;
} int main(){
t[1]=1;
for(int i=2;i<=20;++i)t[i]=t[i-1]*10;
scanf("%d",&T);
memset(f,-1,sizeof(f));
while(T--){
scanf("%lld%lld",&L,&R);
check(R);
R=DFS(len,0,0,1);
check(L-1);
L=DFS(len,0,0,1);
printf("%lld\n",R-L);
}return 0;
}

poj 3252

求有多少个二进制串中0的数量不少于1的数量

状态是很显然的,f[i][j]表示长度为i,1的数量为j

然后直接裸上DP就可以了,注意此时前导零对答案会有影响

所以我采用的方法是<len的直接预处理计算

=len的采用记忆化搜索

其实可以直接记忆化搜索,在多传一个参数表示是否是首位就可以啦

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<algorithm>
using namespace std; int L,R;
int Num[35],len=0;
int f[35][35],ans;
int dp[35][35]; void check(int n){
memset(Num,0,sizeof(Num));len=0;
if(!n){Num[++len]=0;return;}
while(n)Num[++len]=(n&1),n>>=1;
}
int DFS(int pos,int num_one,int flag){
if(!pos){
if(num_one<=(len>>1))return 1;
return 0;
}
if(!flag&&f[pos][num_one]!=-1)return f[pos][num_one];
int tmp=0,u=flag?Num[pos]:1;
for(int i=(pos==len?1:0);i<=u;++i){
tmp=tmp+DFS(pos-1,num_one+(i==1),flag&&i==u);
}
return flag?tmp:f[pos][num_one]=tmp;
} int main(){
scanf("%d%d",&L,&R);
dp[1][1]=1;
for(int i=1;i<32;++i){
for(int j=0;j<=i;++j){
dp[i+1][j+1]+=dp[i][j];
dp[i+1][j]+=dp[i][j];
}
}
check(R);memset(f,-1,sizeof(f));
R=DFS(len,0,1);
for(int i=1;i<len;++i){
for(int j=0;j<=(i>>1);++j)R+=dp[i][j];
}
check(L-1);memset(f,-1,sizeof(f));
L=DFS(len,0,1);
for(int i=1;i<len;++i){
for(int j=0;j<=(i>>1);++j)L+=dp[i][j];
}
printf("%d\n",R-L);
return 0;
}

hdu 3709

定义平衡数是存在一个重心,使得左右连边的权重和相等的数

求给定区间内有多少个平衡数

首先我们可以证明在没有前导零的情况下,一个平衡数最多只有一个重心

因为重心移动一定是一边加上一个正整数,另一边减去一个正整数,不可能存在继续平衡的可能性

那么由于位数很小,我们不妨枚举重心,我们发现实际上可能的权重和也很小

就设f[i][j]表示位数为i,权重和为j的方案就可以啦

然后如果考虑不考虑前导零的话,最后还要减去000000这种情况

由于L>=0,所以要特判L=0的情况

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std; typedef long long LL;
int T,now,cur;
LL L,R;
LL f[22][3010];
int Num[22],len=0; void check(LL n){
memset(Num,0,sizeof(Num));len=0;
if(!n){Num[++len]=0;return;}
while(n)Num[++len]=n%10,n/=10;
}
LL DFS(int pos,int sum,int flag){
if(!pos){
if(sum==0)return 1;
return 0;
}
if(!flag&&f[pos][sum]!=-1)return f[pos][sum];
LL tmp=0;int u=flag?Num[pos]:9;
for(int i=0;i<=u;++i){
int QAQ=sum+i*(pos-now);
if(QAQ<0)continue;
tmp+=DFS(pos-1,QAQ,flag&&i==u);
}return flag?tmp:f[pos][sum]=tmp;
} int main(){
scanf("%d",&T);
while(T--){
scanf("%lld%lld",&L,&R);
check(R);R=0;cur=len;
for(int i=1;i<=len;++i){
now=i;
memset(f,-1,sizeof(f));
R+=DFS(len,0,1);
}
if(L==0)L=0,len=1;
else{
check(L-1);L=0;
for(int i=1;i<=len;++i){
now=i;
memset(f,-1,sizeof(f));
L+=DFS(len,0,1);
}
}printf("%lld\n",R-L-(cur-len));
}return 0;
}

SPOJ BALNUM

求有多少个数字出现的每个偶数数字出现了奇数次,出现的每个奇数数字出现了偶数次

我们发现一个数字最多只有3种情况,没出现,出现奇数次,出现偶数次

用三进制表示状态压缩即可,自己的代码写的略丑

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<iostream>
using namespace std; typedef long long LL;
int T;
int Num[22],len=0;
bool vis[60010];
int code[12];
LL f[22][60010];
LL L,R; //0 没出现 1 出现奇数 2 出现偶数 void decode(int S,int *code){
for(int i=0;i<=9;++i){
code[i]=S%3;S/=3;
}return;
}
int encode(int *code){
int S=0;
for(int i=9;i>=0;--i)S=S*3+code[i];
return S;
}
bool judge(int S){
decode(S,code);
for(int i=0;i<=9;++i){
if(i&1){
if(code[i]==1)return false;
}else if(code[i]==2)return false;
}return true;
}
void check(LL n){
memset(Num,0,sizeof(Num));len=0;
if(!n){Num[++len]=0;return;}
while(n)Num[++len]=n%10,n/=10;
}
LL DFS(int pos,int S,int flag,int first){
if(!pos){
if(vis[S])return 1;
return 0;
}
if(!flag&&f[pos][S]!=-1)return f[pos][S];
LL tmp=0;int u=flag?Num[pos]:9;
int ch[12];
for(int i=0;i<=u;++i){
decode(S,ch);
if(i==0&&first);
else{
if(ch[i]==0)ch[i]=1;
else if(ch[i]==1)ch[i]=2;
else ch[i]=1;
}
tmp+=DFS(pos-1,encode(ch),flag&&i==u,first&&i==0);
}return flag?tmp:f[pos][S]=tmp;
} int main(){
scanf("%d",&T);
for(int i=0;i<59049;++i)if(judge(i))vis[i]=true;
while(T--){
scanf("%lld%lld",&L,&R);
check(R);memset(f,-1,sizeof(f));
R=DFS(len,0,1,1);
check(L-1);memset(f,-1,sizeof(f));
L=DFS(len,0,1,1);
printf("%lld\n",R-L);
}return 0;
}

BZOJ 1026

windy数,没什么好说的

只是实验一下新模板的效果

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
using namespace std; int L,R;
int f[12][12];
int dp[12][12];
int Num[12],len=0; void check(int n){
memset(Num,0,sizeof(Num));len=0;
if(!n){Num[++len]=0;return;}
while(n)Num[++len]=n%10,n/=10;
}
int DFS(int pos,int la,int flag){
if(!pos)return 1;
if(!flag&&f[pos][la]!=-1)return f[pos][la];
int tmp=0,u=flag?Num[pos]:9;
for(int i=(pos==len?1:0);i<=u;++i){
if(pos!=len&&abs(i-la)<2)continue;
tmp+=DFS(pos-1,i,flag&&i==u);
}return flag?tmp:f[pos][la]=tmp;
} int main(){
scanf("%d%d",&L,&R);
for(int i=1;i<=9;++i)dp[1][i]=1;
for(int i=1;i<10;++i){
for(int j=0;j<=9;++j){
if(dp[i][j]){
for(int k=0;k<=9;++k){
if(abs(j-k)<2)continue;
dp[i+1][k]+=dp[i][j];
}
}
}
}
memset(f,-1,sizeof(f));
check(R);R=DFS(len,0,1);
for(int i=1;i<len;++i)for(int j=0;j<=9;++j)R+=dp[i][j];
check(L-1);L=DFS(len,0,1);
for(int i=1;i<len;++i)for(int j=0;j<=9;++j)L+=dp[i][j];
printf("%d\n",R-L);
return 0;
}

BZOJ 4521

CQOI的模板题目,限定11位真是兹磁啊,留下了L=10^11的大陷阱

加一维表示上一个数是多少

再加一维0/1/2/3表示连续的数字个数

再加一维表示是否有8,再加一维表示是否有4

模板写起来就好啦

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cstdlib>
#include<algorithm>
using namespace std; typedef long long LL;
int Num[13],len=0;
LL L,R;
LL f[13][10][4][2][2];// 位数 0/1/2/3 是否有8 是否有4 void check(LL n){
memset(Num,0,sizeof(Num));len=0;
if(!n){Num[++len]=0;return;}
while(n)Num[++len]=n%10,n/=10;
}
LL DFS(int pos,int la,int go_num,int eight,int four,int flag){
if(!pos){
if((go_num!=3)||(eight&&four))return 0;
return 1;
}
if(!flag&&f[pos][la][go_num][eight][four]!=-1)return f[pos][la][go_num][eight][four];
LL tmp=0;int u=flag?Num[pos]:9;
for(int i=(pos==len?1:0);i<=u;++i){
int now=go_num;
if(now==3);
else{
if(i==la)now++;
else now=1;
}
tmp+=DFS(pos-1,i,now,eight|(i==8),four|(i==4),flag&&i==u);
}return flag?tmp:f[pos][la][go_num][eight][four]=tmp;
} int main(){
scanf("%lld%lld",&L,&R);
memset(f,-1,sizeof(f));
check(R);R=DFS(len,0,0,0,0,1);
check(L-1);
if(len==11)L=DFS(len,0,0,0,0,1);
else L=0;
printf("%lld\n",R-L);
return 0;
}

hdu 4734

一开始被吓到了,开始考虑如何暴力的时候很惊讶的发现F(x)的范围很小,不到20000

那么我们缀一维表示F(x)的值就可以啦

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<algorithm>
using namespace std; typedef long long LL;
int T,fit,A,B,kase;
int f[12][20010];
int Num[12],len=0; void check(int n){
memset(Num,0,sizeof(Num));len=0;
if(!n){Num[++len]=0;return;}
while(n)Num[++len]=n%10,n/=10;
}
int DFS(int pos,int sum,int flag){
if(!pos)return sum>=0;
if(sum<0)return 0;
if(!flag&&f[pos][sum]!=-1)return f[pos][sum];
int tmp=0,u=flag?Num[pos]:9;
for(int i=0;i<=u;++i){
tmp+=DFS(pos-1,sum-i*(1<<(pos-1)),flag&&i==u);
}return flag?tmp:f[pos][sum]=tmp;
} int main(){
scanf("%d",&T);
memset(f,-1,sizeof(f));
while(T--){
scanf("%d%d",&A,&B);kase++;
check(A);fit=0;
for(int i=1;i<=len;++i)fit=fit+Num[i]*(1<<(i-1));
check(B);B=DFS(len,fit,1);
printf("Case #%d: %d\n",kase,B);
}return 0;
}

hdu 4507

不能有7,转移的时候直接判

缀两维表示两个限制%7的余数

唯一的问题是求平方和

式子参照我在cojs上出的题目就可以啦

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
using namespace std; typedef long long LL;
const int mod=1e9+7;
int T;
int Num[22],len=0;
LL L,R;
LL t[22];
struct num{
LL s0,s1,s2;
void clear(){s0=s1=s2=0;}
}f[22][7][7]; void check(LL n){
memset(Num,0,sizeof(Num));len=0;
if(!n){Num[++len]=0;return;}
while(n)Num[++len]=n%10,n/=10;
}
num DFS(int pos,int num_mod,int sum_mod,int flag){
if(!pos){
num tmp;tmp.clear();
if(num_mod!=0&&sum_mod!=0)tmp.s0=1;
else tmp.s0=0;
return tmp;
}
if(!flag&&f[pos][num_mod][sum_mod].s0!=-1)return f[pos][num_mod][sum_mod];
num tmp;tmp.clear();
int u=flag?Num[pos]:9;
for(int i=0;i<=u;++i){
if(i==7)continue;
num now=DFS(pos-1,(num_mod+i*t[pos])%7,(sum_mod+i)%7,flag&&i==u);
LL sum=i*t[pos]%mod;
tmp.s0=tmp.s0+now.s0;if(tmp.s0>=mod)tmp.s0-=mod; tmp.s1=tmp.s1+now.s1;if(tmp.s1>=mod)tmp.s1-=mod;
tmp.s1=tmp.s1+now.s0*sum%mod;if(tmp.s1>=mod)tmp.s1-=mod; tmp.s2=tmp.s2+now.s2;if(tmp.s2>=mod)tmp.s2-=mod;
tmp.s2=tmp.s2+now.s1*sum%mod*2%mod;if(tmp.s2>=mod)tmp.s2-=mod;
tmp.s2=tmp.s2+now.s0*sum%mod*sum%mod;if(tmp.s2>=mod)tmp.s2-=mod;
}return flag?tmp:f[pos][num_mod][sum_mod]=tmp;
} int main(){
t[1]=1;
for(int i=2;i<=20;++i)t[i]=t[i-1]*10;
scanf("%d",&T);
memset(f,-1,sizeof(f));
while(T--){
scanf("%lld%lld",&L,&R);
check(R);num A=DFS(len,0,0,1);
check(L-1);num B=DFS(len,0,0,1);
printf("%lld\n",(A.s2-B.s2+mod)%mod);
}return 0;
}

hdu 4352

求[L,R]中有多少个数字的LIS恰好为K

首先注意到LIS最大是10,我们考虑如何求LIS

我们nlogn求LIS的时候对于每个长度记录最小的结尾即可

这样我们就可以状态压缩了

压缩的原理是这样的:如果某个数字i对应压缩位为1,那么[0,i]中有多少个1就是长度,这个数字表示这个长度的最小结尾

每次数位DP增加一个数字后更新即可

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
using namespace std; typedef long long LL;
int T,k,kase;
int Num[22],len=0;
int num[1024];
LL L,R;
LL f[22][1024][11]; void check(LL n){
memset(Num,0,sizeof(Num));len=0;
if(!n){Num[++len]=0;return;}
while(n)Num[++len]=n%10,n/=10;
}
void encode(int &S,int pos){
for(int i=pos;i<=9;++i){
if(S>>i&1){S^=(1<<i);break;}
}S|=(1<<pos);return;
}
LL DFS(int pos,int S,int flag,int first){
if(!pos)return num[S]==k;
if(!flag&&f[pos][S][k]!=-1)return f[pos][S][k];
LL tmp=0;int u=flag?Num[pos]:9;
for(int i=0;i<=u;++i){
int now=S;
if(first&&i==0);
else encode(now,i);
tmp+=DFS(pos-1,now,flag&&i==u,first&&i==0);
}return flag?tmp:f[pos][S][k]=tmp;
} int main(){
scanf("%d",&T);
for(int i=1;i<1024;++i)num[i]=num[i>>1]+(i&1);
memset(f,-1,sizeof(f));
while(T--){
scanf("%lld%lld",&L,&R);
scanf("%d",&k);kase++;
check(R);R=DFS(len,0,1,1);
check(L-1);L=DFS(len,0,1,1);
printf("Case #%d: %lld\n",kase,R-L);
}return 0;
}

ZOJ 3494

给定一个把十进制数字转化的方法

之后给定若干的禁止串,求[L,R]中不含禁止串的数字有多少个

建立AC自动机,之后设f[i][j]表示走了i步走到了AC自动机的j节点

预处理判断是否合法就可以了

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<queue>
using namespace std; typedef long long LL;
const int maxn=2010;
const int mod=1000000009;
int T,n,len;
char s[maxn];
char Num[maxn];
int Go[maxn][10];
LL f[210][maxn];
LL L,R;
queue<int>Q;
struct trie{
int cnt;
int next[maxn][2];
int fail[maxn];
bool vis[maxn];
void init(){
cnt=1;fail[1]=0;vis[1]=false;
next[1][0]=next[1][1]=0;
}
int Newnode(){
++cnt;next[cnt][0]=next[cnt][1]=0;
fail[cnt]=0;vis[cnt]=false;return cnt;
}
void insert(){
int L=strlen(s+1),now=1;
for(int i=1;i<=L;++i){
int nxt=s[i]-'0';
if(!next[now][nxt])next[now][nxt]=Newnode();
now=next[now][nxt];
}vis[now]=true;return;
}
void build_fail(){
Q.push(1);fail[1]=0;
while(!Q.empty()){
int u=Q.front();Q.pop();
vis[u]|=vis[fail[u]];
for(int i=0;i<2;++i){
int k=fail[u];
while(k&&!next[k][i])k=fail[k];
if(next[u][i]){
fail[next[u][i]]=k?next[k][i]:1;
Q.push(next[u][i]);
}else next[u][i]=k?next[k][i]:1;
}
}return;
}
int Let_Go(int now,int num){
if(vis[now])return -1;
for(int i=3;i>=0;--i){
now=next[now][num>>i&1];
if(vis[now])return -1;
}return now;
}
}AC;
void Get_Pre(){
memset(f,-1,sizeof(f));
for(int i=1;i<=AC.cnt;++i){
for(int j=0;j<=9;++j){
Go[i][j]=AC.Let_Go(i,j);
}
}return;
}
void flip(){for(int i=1;i<=len;++i)Num[len-i+1]=s[i]-'0';}
LL DFS(int pos,int S,int flag,int first){
if(!pos)return 1;
if(!flag&&!first&&f[pos][S]!=-1)return f[pos][S];
LL ans=0;
if(first){
ans+=DFS(pos-1,S,flag&&Num[pos]==0,true);
if(ans>=mod)ans-=mod;
}else{
if(Go[S][0]!=-1)ans+=DFS(pos-1,Go[S][0],flag&&Num[pos]==0,false);
if(ans>=mod)ans-=mod;
}
int u=flag?Num[pos]:9;
for(int i=1;i<=u;++i){
if(Go[S][i]!=-1){
ans+=DFS(pos-1,Go[S][i],flag&&i==u,false);
if(ans>=mod)ans-=mod;
}
}return (flag||first)?ans:f[pos][S]=ans;
} int main(){
scanf("%d",&T);
while(T--){
AC.init();
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%s",s+1);
AC.insert();
}AC.build_fail();
Get_Pre();
scanf("%s",s+1);
len=strlen(s+1);
for(int i=len;i>=1;--i){
if(s[i]>'0'){s[i]--;break;}
s[i]='9';
}
flip();L=DFS(len,1,1,1);
scanf("%s",s+1);
len=strlen(s+1);
flip();R=DFS(len,1,1,1);
printf("%lld\n",(R-L+mod)%mod);
}return 0;
}

BZOJ 3530

上面那道题的弱化版,直接写就可以啦

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std; typedef long long LL;
const int maxn=1510;
const int mod=1e9+7;
int n,len;
char s[maxn];
char p[maxn];
char Num[maxn];
queue<int>Q;
int Go[maxn][10];
LL f[maxn][maxn];
LL ans;
struct trie{
int cnt;
int next[maxn][10];
int fail[maxn];
bool vis[maxn];
void init(){cnt=1;}
void insert(){
int L=strlen(p+1),now=1;
for(int i=1;i<=L;++i){
int nxt=p[i]-'0';
if(!next[now][nxt])next[now][nxt]=++cnt;
now=next[now][nxt];
}vis[now]=true;return;
}
void build_fail(){
Q.push(1);
while(!Q.empty()){
int u=Q.front();Q.pop();
vis[u]|=vis[fail[u]];
for(int i=0;i<10;++i){
int k=fail[u];
while(k&&!next[k][i])k=fail[k];
if(next[u][i]){
fail[next[u][i]]=k?next[k][i]:1;
Q.push(next[u][i]);
}else next[u][i]=k?next[k][i]:1;
}
}return;
}
LL DFS(int pos,int S,int flag,int first){
if(!pos)return 1;
if(!flag&&!first&&f[pos][S]!=-1)return f[pos][S];
LL tmp=0;
if(first){
tmp+=DFS(pos-1,S,flag&&Num[pos]==0,1);
if(tmp>=mod)tmp-=mod;
}else{
if(!vis[next[S][0]]){
tmp+=DFS(pos-1,next[S][0],flag&&Num[pos]==0,0);
if(tmp>=mod)tmp-=mod;
}
}
int u=flag?Num[pos]:9;
for(int i=1;i<=u;++i){
if(!vis[next[S][i]]){
tmp+=DFS(pos-1,next[S][i],flag&&i==Num[pos],0);
if(tmp>=mod)tmp-=mod;
}
}return (flag||first)?tmp:f[pos][S]=tmp;
}
}AC;
void flip(){for(int i=1;i<=len;++i)Num[len-i+1]=s[i]-'0';}
int main(){
scanf("%s",s+1);
AC.init();scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%s",p+1);
AC.insert();
}AC.build_fail();
len=strlen(s+1);
memset(f,-1,sizeof(f));
flip();ans=AC.DFS(len,1,1,1);
printf("%lld\n",(ans-1+mod)%mod);
return 0;
}

BZOJ 3209

枚举1的个数然后做数位DP,之后快速幂乘起来就好啦

值得一提的是这个题可以直接用组合数算数位DP的结果

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<algorithm>
using namespace std; typedef long long LL;
const int mod=10000007;
LL n,ans;
LL f[72][72];
int Num[72],len=0;
LL pow_mod(LL v,LL p){
LL tmp=1;
while(p){
if(p&1)tmp=tmp*v%mod;
v=v*v%mod;p>>=1;
}return tmp;
}
void check(LL n){
memset(Num,0,sizeof(Num));len=0;
if(!n){Num[++len]=0;return;}
while(n)Num[++len]=(n&1),n>>=1;
}
LL DFS(int pos,int one,int flag){
if(one<0)return 0;
if(!pos){
if(!one)return 1;
return 0;
}
if(!flag&&f[pos][one]!=-1)return f[pos][one];
LL tmp=0;int u=flag?Num[pos]:1;
for(int i=0;i<=u;++i){
tmp=tmp+DFS(pos-1,one-(i==1),flag&&i==u);
}return flag?tmp:f[pos][one]=tmp;
} int main(){
scanf("%lld",&n);
check(n);ans=1;memset(f,-1,sizeof(f));
for(int i=1;i<=len;++i){
LL now=DFS(len,i,1);
ans=ans*pow_mod(1LL*i,now)%mod;
}printf("%lld\n",ans);
return 0;
}

BZOJ 3329

x^3x=2x等价于x^2x=3x

我们知道异或是不进位加法

又因为x+2x=3x

所以当且仅当满足x中任意相邻两个数不都是1才是方程的一组解

即x&(x<<1)=0

对于第一问我们直接做数位DP就可以了

第二问我们设f[i][0/1]表示i位且上一位是0/1

f[i][0]=f[i-1][0]+f[i-1][1]

f[i][1]=f[i-1][0]

之后直接矩阵乘法就可以了

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<algorithm>
using namespace std; typedef long long LL;
const int mod=1e9+7;
int T;
int Num[72],len=0;
LL f[72][2];//上一位是多少
LL n,sum;
struct Matrix{
LL a[2][2];
Matrix(){memset(a,0,sizeof(a));}
}A,ans;
void check(LL n){
memset(Num,0,sizeof(Num));len=0;
if(!n){Num[++len]=0;return;}
while(n)Num[++len]=(n&1),n>>=1;
}
LL DFS(int pos,int la,int flag){
if(!pos)return 1;
if(!flag&&f[pos][la]!=-1)return f[pos][la];
LL tmp=0;int u=flag?Num[pos]:1;
for(int i=0;i<=u;++i){
if(la==1&&i==1)continue;
tmp=tmp+DFS(pos-1,i,flag&&i==u);
}return flag?tmp:f[pos][la]=tmp;
}
void build_Matrix(){
A.a[0][0]=1;A.a[0][1]=1;
A.a[1][0]=1;A.a[1][1]=0;
}
Matrix operator *(const Matrix &A,const Matrix &B){
Matrix C;
for(int i=0;i<2;++i){
for(int j=0;j<2;++j){
for(int k=0;k<2;++k){
C.a[i][j]=C.a[i][j]+A.a[i][k]*B.a[k][j]%mod;
if(C.a[i][j]>=mod)C.a[i][j]-=mod;
}
}
}return C;
}
Matrix pow_mod(Matrix v,LL p){
Matrix tmp;
for(int i=0;i<2;++i)tmp.a[i][i]=1;
while(p){
if(p&1)tmp=tmp*v;
v=v*v;p>>=1;
}return tmp;
} int main(){
scanf("%d",&T);
memset(f,-1,sizeof(f));
build_Matrix();
while(T--){
scanf("%lld",&n);
check(n);
sum=DFS(len,0,1);
printf("%lld\n",sum-1);
ans.a[0][0]=1;ans.a[0][1]=0;
ans=ans*pow_mod(A,n);
printf("%lld\n",(ans.a[0][1]+ans.a[0][0])%mod);
}return 0;
}

hdu 3943

求(L,R]第k个nya数,nya数定义为恰好有x个4和y个7的数

我们二分之后问题转化成了数位DP,直接做就可以了,略坑的是这个区间是左开右闭的

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
using namespace std; typedef long long LL;
int T,x,y,n,kase;
int Num[22],len=0;
LL P,Q,L,R,k;
LL f[22][22][22]; void check(LL n){
memset(Num,0,sizeof(Num));len=0;
if(!n){Num[++len]=0;return;}
while(n)Num[++len]=n%10,n/=10;
}
LL DFS(int pos,int four,int seven,int flag){
if(four<0||seven<0)return 0;
if(!pos){
if(!four&&!seven)return 1;
return 0;
}
if(!flag&&f[pos][four][seven]!=-1)return f[pos][four][seven];
LL tmp=0;int u=flag?Num[pos]:9;
for(int i=0;i<=u;++i){
tmp=tmp+DFS(pos-1,four-(i==4),seven-(i==7),flag&&i==u);
}return flag?tmp:f[pos][four][seven]=tmp;
} int main(){
scanf("%d",&T);
memset(f,-1,sizeof(f));
while(T--){
scanf("%lld%lld",&P,&Q);
scanf("%d%d",&x,&y);kase++;
check(P);L=DFS(len,x,y,1);
check(Q);R=DFS(len,x,y,1);
printf("Case #%d:\n",kase);
scanf("%d",&n);
while(n--){
scanf("%lld",&k);
if(R-L<k){printf("Nya!\n");continue;}
LL l=P,r=Q;
while(l<r){
LL mid=(l+r)>>1;
check(mid);
LL ans=DFS(len,x,y,1);
if(ans-L<k)l=mid+1;
else r=mid;
}printf("%lld\n",r);
}
}return 0;
}

BZOJ 2757

求[L,R]中各位数字的乘积为k的数有多少个

首先我们会发现k只会有2,3,5,7这4个素因子

而进一步我们很容易发现满足这个条件的k是很少的

那么我们可以把满足条件的k哈希掉,数位DP即可

注意当k=0时,数位中只要至少有一个0就可以了,我采取的处理方法是又写了另外一个数位DP

这道题目我的代码略丑,其实一开始处理出来所以合法的k是最好的,在中间过程处理的话会变麻烦

至于求数字和,都求过平方和了这就随意做了

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std; typedef long long LL;
const int mod=20120427;
const int maxn=300010;
const int MOD=1333331;
int T;
LL L,R,k;
LL t[22];
struct num{
LL s0,s1;
void clear(){s0=s1=0;}
}dp[22][2],f[22][maxn],A,B;
int Num[22],len=0;
struct HASHMAP{
int cnt;
int h[MOD+10],next[maxn];
LL st[maxn];
int ask(LL S){
int key=S%MOD;
for(int i=h[key];i;i=next[i]){
if(st[i]==S)return i;
}
++cnt;next[cnt]=h[key];h[key]=cnt;
st[cnt]=S;return cnt;
}
}H; void check(LL n){
memset(Num,0,sizeof(Num));len=0;
if(!n){Num[++len]=0;return;}
while(n)Num[++len]=n%10,n/=10;
}
num DP(int pos,int zero,int flag,int first){
if(!pos){
num tmp;tmp.clear();
tmp.s0=zero;
return tmp;
}
if(!flag&&!first&&dp[pos][zero].s0!=-1)return dp[pos][zero];
num tmp,now;tmp.clear();
if(first){
now=DP(pos-1,0,0,1);
tmp.s0=tmp.s0+now.s0;if(tmp.s0>=mod)tmp.s0-=mod;
tmp.s1=tmp.s1+now.s1;if(tmp.s1>=mod)tmp.s1-=mod;
}else{
now=DP(pos-1,1,flag&&Num[pos]==0,0);
tmp.s0=tmp.s0+now.s0;if(tmp.s0>=mod)tmp.s0-=mod;
tmp.s1=tmp.s1+now.s1;if(tmp.s1>=mod)tmp.s1-=mod;
}
int u=flag?Num[pos]:9;
for(int i=1;i<=u;++i){
now=DP(pos-1,zero,flag&&i==u,0);
tmp.s0=tmp.s0+now.s0;if(tmp.s0>=mod)tmp.s0-=mod;
tmp.s1=tmp.s1+now.s1;if(tmp.s1>=mod)tmp.s1-=mod;
tmp.s1=tmp.s1+i*t[pos]*now.s0%mod;if(tmp.s1>=mod)tmp.s1-=mod;
}return (flag||first)?tmp:dp[pos][zero]=tmp;
}
num DFS(int pos,LL mul,int flag,int first){
if(!pos){
num tmp;tmp.clear();
if(!first&&mul==1)tmp.s0=1;
return tmp;
}
int cur=H.ask(mul);
if(!first&&!flag&&f[pos][cur].s0!=-1)return f[pos][cur];
num tmp,now;tmp.clear();
if(first){
now=DFS(pos-1,mul,0,first);
tmp.s0=tmp.s0+now.s0;if(tmp.s0>=mod)tmp.s0-=mod;
tmp.s1=tmp.s1+now.s1;if(tmp.s1>=mod)tmp.s1-=mod;
}
int u=flag?Num[pos]:9;
for(int i=1;i<=u;++i){
if(mul%i==0){
now=DFS(pos-1,mul/i,flag&&i==u,0);
tmp.s0=tmp.s0+now.s0;if(tmp.s0>=mod)tmp.s0-=mod;
tmp.s1=tmp.s1+now.s1;if(tmp.s1>=mod)tmp.s1-=mod;
tmp.s1=tmp.s1+i*t[pos]*now.s0%mod;if(tmp.s1>=mod)tmp.s1-=mod;
}
}return (flag||first)?tmp:f[pos][cur]=tmp;
}
bool judge(LL k){
for(int i=2;i<=9;++i){
while(k%i==0)k/=i;
}return k>1;
}
int main(){
scanf("%d",&T);
t[1]=1;
for(int i=2;i<=20;++i)t[i]=t[i-1]*10%mod;
memset(f,-1,sizeof(f));
memset(dp,-1,sizeof(dp));
while(T--){
scanf("%lld%lld%lld",&L,&R,&k);
if(k==0){
check(L-1);A=DP(len,0,1,1);
check(R);B=DP(len,0,1,1);
}else{
if(judge(k)){printf("0\n");continue;}
check(L-1);A=DFS(len,k,1,1);
check(R);B=DFS(len,k,1,1);
}printf("%lld\n",(B.s1-A.s1+mod)%mod);
}return 0;
}

BZOJ 3131

跟上面的题思路是一样的,先预处理出所有合法的k

设s[i]表示第i个合法的k在[1,n]中有多少乘积为k的数,做数位DP

之后用优先队列实现多路归并即可,循环K次即可

注意放进队列里的时候不要取模

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std; typedef long long LL;
const int mod=1e9+7;
const int maxn=200010;
const int MOD=1333331;
LL n;
LL f[22][20010];
LL s[20010];
int Num[22],len,k;
struct pos{
int a,b;
LL num;
pos(int a=0,int b=0,LL num=0):a(a),b(b),num(num){}
bool operator <(const pos &A)const{
return num<A.num;
}
};
priority_queue<pos>Q;
bool cmp(const LL &A,const LL &B){return A>B;}
struct HASHMAP{
int cnt;
int h[MOD+10],next[maxn];
LL st[maxn];
void insert(LL S){
int key=S%MOD;
for(int i=h[key];i;i=next[i]){
if(st[i]==S)return;
}
++cnt;next[cnt]=h[key];h[key]=cnt;
st[cnt]=S;return;
}
int ask(LL S){
int key=S%MOD;
for(int i=h[key];i;i=next[i]){
if(st[i]==S)return i;
}return 0;
}
}H;
void check(LL n){
memset(Num,0,sizeof(Num));len=0;
if(!n){Num[++len]=0;return;}
while(n)Num[++len]=n%10,n/=10;
}
LL DFS(int pos,LL mul,int flag,int first){
if(!pos){
if(!first&&mul==1)return 1;
return 0;
}
int cur=H.ask(mul);
if(!flag&&!first&&f[pos][cur]!=-1)return f[pos][cur];
LL tmp=0;
if(first)tmp=tmp+DFS(pos-1,mul,0,1);
int u=flag?Num[pos]:9;
for(int i=1;i<=u;++i){
if(mul%i==0)tmp=tmp+DFS(pos-1,mul/i,flag&&i==u,0);
}return (flag||first)?tmp:f[pos][cur]=tmp;
}
LL mul(LL a,LL b){
LL s=0;
while(b){
if(b&1)s=(s+a)%mod;
a=(a<<1)%mod;b>>=1;
}return s;
} int main(){
scanf("%lld%d",&n,&k);
check(n);memset(f,-1,sizeof(f));
for(int i=1;i<=9;++i)H.insert(i);
for(int i=2;i<=len;++i){
int now=H.cnt;
for(int k=1;k<=now;++k){
LL S=H.st[k];
for(int j=1;j<=9;++j)H.insert(S*j);
}
}
for(int i=1;i<=H.cnt;++i){
LL S=H.st[i];
if(S>n)continue;
s[i]=DFS(len,S,1,1);
}
sort(s+1,s+H.cnt+1,cmp);
for(int i=1;i<=H.cnt;++i){
Q.push(pos(i,1,s[i]*s[1]));
}
LL ans=0;
for(int i=1;i<=k;++i){
if(Q.empty())break;
pos tmp=Q.top();Q.pop();
ans=ans+tmp.num%mod;
if(ans>=mod)ans-=mod;
if(tmp.b+1<=H.cnt){
Q.push(pos(tmp.a,tmp.b+1,s[tmp.a]*s[tmp.b+1]));
}
}printf("%lld\n",ans);
return 0;
}

留下两道题目当做最后复习:一道是BZOJ的大新闻,一道是CF的题

关于今天换的模板的一些总结:

1、首先对于flag=1的时候我们不需要记忆化,因为flag=1的状态只会转移到一个唯一的状态

2、使用这个模板要在DFS的过程中判掉几乎所有的限制,然后由于记录的只是flag=0的时候的状态,所以与L和R无关

每次DP不需要清空

3、有关于前导零的处理:我是有三种方法的:

第一种:当前导零对答案无影响时,补全前导零直接DP

第二种:先DP预处理,之后查询的时候先加上<len的,再去记忆化解决=len的

第三种: 增加传参first,表示当前是否是首位来判断对于状态是否有贡献,至于是否对first进行记忆化要根据题目来定

通常情况下不需要对first记忆化

4、有关于一些坑点:

数位DP一个最需要注意的问题是要注意整数溢出

其次要考虑一些边界情况,譬如当L=0的时候,如果要Solve(R)-Solve(L-1)就挂掉了

之后考虑题目的性质是否可减,通常情况下是可减的,我们转化成Solve(R)-Solve(L-1)

5、关于模板的一些细节:

注意在判断是否已经记忆化之前要判断flag

注意记忆化的时候要判断flag

注意每次循环的上界要判断flag

6、对于状态,数位DP的状态一般很好想

但是精简的状态可能会要动些脑子,一定要考虑我们到底需要记录什么

另外数位DP写的时候一定要细心,考虑所有边界,争取一遍写对

然后考试的时候数位DP一定要拍,错的话要耐心去想去改

未完待更(以后要做一些更好的数位DP,做一做论文题)

八月份!三个月!Fighting!

My_Plan part1 小结的更多相关文章

  1. 20181117-python第二章学习小结-part1

    什么是二进制,十进制如何转化成二进制. 在python上可使用简单的函数进行转化,bin() 数据量的基本关系: 1bit  就是0/1的一个单位 1bytes = 8bit    #1个字节,就是一 ...

  2. 20181115 python-第一章学习小结part1

    知识点回顾: 什么是编程: 写代码,让计算机执行任务 编程语言的分类与特性: 1.机器语言,即二进制语言,最帖近于机器底层,可以由计算机直接执行,故速度最快,但不适合开发. 2.汇编语言,直接将二进制 ...

  3. [Blog] Part1: 技术札记-写个创站小结吧

    创站绝对是一个大坑 我当初真有勇气.. 嗯 这个站主要就是 Github+Jekyll+markdown 基本上还是现在能用的比较习惯的模式 基本流程概述 域名 -> 修改DNS -> g ...

  4. 【Hadoop】HIVE 小结概览

    一.HIVE概览小结 二.HIVE安装 Hive只在一个节点上安装即可 .上传tar包 .解压 tar -zxvf hive-.tar.gz -C /cloud/ .配置mysql metastore ...

  5. 小课堂Week12 Clean Code Part1

    小课堂Week12 Clean Code Part1 今天的主题是函数,让我们看一个函数,找一找其中的"不整洁". 我们也根据这段代码,讨论下对于整洁代码的两个重要原则. publ ...

  6. 小课堂Week8 例外处理设计的逆袭Part1

    小课堂Week8 例外处理设计的逆袭Part1 今天和大家讲一本书,书名是<例外处理设计的逆袭>. 为什么想讲这本书,是因为,例外处理在程序代码中到处存在,但是这些到底该如何写好,总觉得有 ...

  7. 使用Unity创建塔防游戏(Part1)

    How to Create a Tower Defense Game in Unity - Part1 原文作者:Barbara Reichart 文章原译:http://www.cnblogs.co ...

  8. 软工作业-----Alpha版本第一周小结

            软工作业-----Alpha版本第一周小结   Part1.第一周周计划记录 姓名 学号 周前计划安排 每周工作记录 自我打分 yrz(队长) 1417 1.进行任务分析 2.任务分配 ...

  9. 第七章:网络优化与正则化(Part1)

    任何数学技巧都不能弥补信息的缺失. --科尼利厄斯·兰佐斯(Cornelius Lanczos) 匈牙利数学家.物理学家 文章相关 1 第七章:网络优化与正则化(Part1) 2 第七章:网络优化与正 ...

随机推荐

  1. 鼠标悬浮图片时弹出透明提示图层的jQuery特效

    源码: <!doctype html> <html class="no-js" lang="en"> <head> < ...

  2. Apache 的 Rewrite 规则详细介绍

    Rewrite标志: R[=code](force redirect) 强制外部重定向 F(force URL to be forbidden) 禁用URL,返回403HTTP状态码 G(force ...

  3. 分享:Perl打开与读取文件的方法

    在Perl中可以用open或者sysopen函数来打开文件进行操作,这两个函数都需要通过一个文件句柄(即文件指针)来对文件进行读写定位等操作. Perl打开与读取文件的方法,供大家学习参考.本文转自: ...

  4. git管理和自动部署项目

    当一个项目需要纳入到版本控制的时候,选择的工具还是比较多的,最常见的就是工具有CVS,SVN,GIT等.在平时的开发中视情况而定,从来就没有最好的版本控制工具,只有最适合的工具.在这里我习惯用git来 ...

  5. GridView数据源绑定的一个小问题

    在使用GridView绑定数据源的时候,遇到了一个问题,因为图简单,没有注意到,贴出错误截图: 找了半天没有找出错误,在网上找了之后,才发现是一个细节引起的错误. 后台是这样写的: namespace ...

  6. c#中的结构与枚举

    结构 与c++不同的是,结构应该定义在命名空间或者类里面,成员变量叫字段,字段并且有访问控制符,每个字段前要加一个下划线 例子 using System; using System.Collectio ...

  7. ActiveMQ之jmscorrelationid与selector

    前面讲过JMSCorrelationID主要是用来关联多个Message,例如需要回复一个消息的时候,通常把回复的消息的JMSCorrelationID设置为原来消息的ID.在下面这个例子中,创建了三 ...

  8. pthread属性设置

    Posix线程中的线程属性pthread_attr_t主要包括scope属性.detach属性.堆栈地址.堆栈大小.优先级.在pthread_create中,把第二个参数设置为NULL的话,将采用默认 ...

  9. MVC 使用 FluentScheduler 定时器计划任务

    MVC 使用 FluentScheduler 定时器计划任务 MacBook Pro 只有四个 USB Type-C 接口是否错了? 一项新技术的诞生总会对已存在的事物造成冲击或影响,如果大家都害怕冲 ...

  10. C/C++中的可变参函数

    可变参函数最好的实例:printf();参数可变 包含的头文件: C语言中:#include<stdarg.h> C++中的可变参的头文件:#include<cstdarg>, ...