数位dp

bzoj3679 数字之积

题目大意:给定n、l、r,f(i)表示i的各位数字之积(不含前导0),求出0<f(i)<=n(l<=i<r)的个数。

思路:这是在夏令营中学长讲的题目,听到一种相对简单的方法。很显然要用1~r的个数-1~l的个数我们先用2、3、5、7(因为每一位都是0~9,所以只能有这几种质数乘起来)dfs出可能的乘积,放在一个数组num中,用f[i][j]表示i位数字(不含前导0)乘积在数组中的下表j的方案数,更新时,用能整除num[j]的k在num中的位置对应的f[i-1][pok]更新答案,然后把这个f数组做成前缀和,就表示下标对应数字<=j的个数了。然后就是数位的部分,把这个数分成一位一位的,首先把长度比这个数小的所有方案都加起来,然后考虑长度一样的,每次穷举严格小于这一位的数,找到可用的乘积在num中的位置,然后加给答案,每一位结束后,就将n除以这一位上的原数。这里有一个问题,就是0,如果这一位是0我们就直接break掉就可以了,因为后面不会有满足要求的答案了(乘起来肯定是0,不符合要求)。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
LL num[100000]={0},f[20][100000]={0},ji[5]={0,2,3,5,7};
int nn[20]={0};
void dfs(int i,LL mul,LL n)
{
    int j;LL mi=1;
    if (i>4) 
    {
      if (mul<=n) num[++num[0]]=mul;
      return;
    }
    for (j=0;j<=50;++j)
    {
        dfs(i+1,mul*mi,n);mi*=ji[i];
        if (mi*mul>n) break;
    }
}
LL work(LL n,LL m)
{
    int i,j,po;LL x,ans=0;
    nn[0]=0;x=n;
    while(x){nn[++nn[0]]=x%10;x/=10;}
    if (nn[0]==0) return 0;
    for (i=1;i<nn[0];++i) ans+=f[i][num[0]];
    for (i=nn[0];i;--i)
    {
       for (j=1;j<nn[i];++j) 
       {
             po=upper_bound(num+1,num+num[0]+1,m/j)-num-1;
          ans+=f[i-1][po];
       }
       if (nn[i]>0) m/=nn[i];
       else break;
    }
    return ans;
}
int main()
{
    int i,j,n,k,up=0;
    long long l,r,x;
    scanf("%d%I64d%I64d",&n,&l,&r);
    dfs(1,1,(LL)n);sort(num+1,num+num[0]+1);
    f[0][1]=1;
    for (i=1;i<=min(9,n);++i) f[1][i]=1;
    x=r;while(x){++up;x/=10;}
    for (i=2;i<=up;++i)
      for (j=1;j<=num[0];++j)
        for (k=1;k<=9;++k)
        {
            if ((int)num[j]%k) continue;
            int po=upper_bound(num+1,num+num[0]+1,num[j]/k)-num-1;
            f[i][j]+=f[i-1][po];
        }
    for (i=0;i<=up;++i)
      for (j=1;j<=num[0];++j)
        f[i][j]+=f[i][j-1];
    printf("%I64d
",work(r,(LL)n)-work(l,(LL)n));
}
View Code

bzoj1026 windy数

题目大意:求a~b内各位数相差不小于2的数的个数。

思路:f[i][j]表示i位数开头为j(可以为0)的个数,g[i][j]表示i位数开头小于等于j的个数。然后查询x,x的位数为x0,答案中首先有所有位数比x0小的和与x0相同但第1位小于x第1位的,然后是从第2位到x0-1位的,跟上一位的相应的关系,但要注意这些都是比当前为小1的可以随便取,如果当前位和上一位不符合要求,就直接break。最后如果能扫到最后一位,然后对最后一位单独操作,因为最后一位能取到和这一位相同的。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int f[11][10]={0},xi[11]={0},g[11][10]={0};
int ab(int x){return x>0 ? x : -x;}
int calc(int x)
{
    int i,j,ans=0,cnt=0; xi[0]=0;
    if (x==0) return 0;
    while(x)
    {
        xi[++xi[0]]=x%10;x/=10;
    }
    for (i=1;i<xi[0];++i) ans+=g[i][9]-g[i][0];
    ans+=g[xi[0]][xi[xi[0]]-1]-g[xi[0]][0];
    for (i=xi[0]-1;i>1;--i)
    {
        if (xi[i]-1>=xi[i+1]+2) ans+=g[i][xi[i]-1]-g[i][xi[i+1]+1];
        if (xi[i+1]-2>=0&&xi[i]>=1) ans+=g[i][min(xi[i+1]-2,xi[i]-1)];
        if (ab(xi[i]-xi[i+1])<2) break;
    }
    if (xi[0]>=2&&i==1)
    {
      if (xi[1]>=xi[2]+2) ans+=g[1][xi[1]]-g[1][xi[2]+1];
      if (xi[i+1]-2>=0) ans+=g[i][min(xi[i+1]-2,xi[i])];
    }
    return ans;
}
int main()
{
    int i,j,k,a,b;
    for (i=0;i<=9;++i) f[1][i]=1; g[1][0]=1;
    for (i=1;i<=9;++i) g[1][i]=g[1][i-1]+f[1][i];
    for (i=2;i<=10;++i)
    {
      for (j=0;j<=9;++j)
        for (k=0;k<=9;++k)
          if (ab(j-k)>=2) f[i][j]+=f[i-1][k];
      g[i][0]=f[i][0];
      for (j=1;j<=9;++j) g[i][j]=g[i][j-1]+f[i][j];
    }
    scanf("%d%d",&a,&b);
    printf("%d
",calc(b)-calc(a-1));
}
View Code

bzoj1833 数字计数

题目大意:统计正整数a~b间每个数(0~9)出现的次数。

思路:f[i][j][t]表示长度为i,开头为j,t这个数字出现的次数。先dp预处理f数组(注意一个高位i上的数字不只是+1,而是加上10^i),然后数位dp,注意一些情况(对于一个高位上的还是不只+1)。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define maxnode 15
#define LL long long
using namespace std;
LL f[maxnode][10][10]={0},ans[10]={0};
int ai[maxnode]={0};
void calc(LL x,int kk)
{
    int i,j,t; LL k,w; ai[0]=0;w=x;k=1;
    while(x){ai[++ai[0]]=x%10;x/=10;k*=10;}
    for (i=1;i<ai[0];++i)
      for (j=0;j<10;++j)
        for (t=1;t<10;++t) ans[j]+=f[i][t][j]*(LL)kk;
    for (i=ai[0];i>=1;--i)
    {
        for (j=(i==ai[0] ? 1 : 0);j<ai[i];++j)
          for (t=0;t<10;++t) ans[t]+=f[i][j][t]*(LL)kk;
        k/=10;ans[ai[i]]+=(w%k+1)*kk;
    }
}
int main()
{
    int i,j,k,t;LL kk,a,b; kk=1;
    for (i=0;i<10;++i) f[1][i][i]=1;
    for (i=2;i<maxnode;++i)
    {
      kk*=(LL)10;
      for (j=0;j<10;++j)
      {
        for (k=0;k<10;++k)
          for (t=0;t<10;++t) f[i][j][t]+=f[i-1][k][t];
        f[i][j][j]+=kk;
      }
    }
    scanf("%I64d%I64d",&a,&b);calc(a-1,-1);calc(b,1);
    for (i=0;i<9;++i) printf("%I64d ",ans[i]);
    printf("%I64d
",ans[9]);
}
View Code

hdu5435 A serious math problem

题目大意:求a~b之间所有数各位异或值的和。

思路:预处理i位数异或值为j的方案数为fi[i][j],然后数位dp就可以了。注意这里数的长度很长用字符串。还有数位dp的时候,每一位都从0~x[i]-1,就可以在一次循环里求出答案了(注意答案中还没有包含原数的异或值。)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define p 1000000007LL
#define maxm 100005
#define LL long long
using namespace std;
LL fi[maxm][16]={0},ai[maxm]={0},bi[maxm]={0};
char s1[maxm],s2[maxm];
LL calc(LL *x){
    LL sum=0;int i,j,k,t=0;
    if (x[0]==1&&x[1]==0) return 0;
    for (i=x[0];i;--i) t^=x[i]; sum=(sum+t)%p;
    for (t=0,i=x[0];i;--i){
        for (j=0;j<16;++j)
          for (k=0;k<x[i];++k) sum=(sum+j*fi[i-1][t^j^k]%p)%p;
        t^=x[i];
    }return sum;
}
int main(){
    int t,i,j,k,l1,l2;scanf("%d",&t);
    fi[0][0]=1LL;
    for (i=1;i<maxm;++i)
        for (j=0;j<16;++j)
          for (k=0;k<10;++k) fi[i][k^j]=(fi[i][k^j]+fi[i-1][j])%p;
    for (k=1;k<=t;++k){
        scanf("%s",&s1);scanf("%s",&s2);
        printf("Case #%d: ",k);
        ai[0]=l1=strlen(s1);bi[0]=l2=strlen(s2);
        for (i=0;i<l1;++i) ai[l1-i]=s1[i]-'0';
        for (i=0;i<l2;++i) bi[l2-i]=s2[i]-'0';
        if (ai[0]==1&&ai[1]==0) printf("%I64d
",calc(bi));
        else{
          --ai[1];
          for (i=1;i<ai[0];++i){
            if (ai[i]<0){ai[i]+=10;--ai[i+1];}
            else break;
          }while(ai[0]>1&&ai[ai[0]]==0) --ai[0];
          printf("%I64d
",((calc(bi)-calc(ai))%p+p)%p);
        }
    }
}
View Code

xjoi20 A

题目大意:求第q个逆序对数为k的n个元素的序列,每一位的顺序按给定的进行。

思路:先dp处理出fi[i][j]i位j个逆序对的个数(注意与inf取min,防止暴long long),然后一位一位的去做。

注意:每一位处理的时候和p的关系;数组大小。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxm 1005
#define maxx 10005
#define LL long long
#define inf 2100000000LL
using namespace std;
LL fi[maxm][maxx]={0},sum[maxm][maxx]={0};
bool use[maxm]={false};
int cnt[maxm]={0},ans[maxm],rank[maxm][maxm]={0};
int main()
{
    int n,k,i,j,p,x,up;
    bool f=false;
    scanf("%d%d%d",&n,&k,&p);
    fi[0][0]=fi[1][0]=1LL;
    for (i=0;i<=k;++i) sum[1][i]=1LL;
    for (i=2;i<=n;++i)
      for (j=0;j<=k;++j){
        up=min(j,i-1);
        fi[i][j]=min(sum[i-1][j]-sum[i-1][j-up-1],inf);
        sum[i][j]=sum[i][j-1]+fi[i][j];
      }if (fi[n][k]<p) printf("-1
");
    else{
        for (i=1;i<=n;++i)
          for (j=1;j<=n;++j) scanf("%d",&rank[i][j]);
        for (i=1;i<=n;++i) cnt[i]=i-1;
        for (i=1;i<=n;++i){
            for (j=1;j<=n;++j){
                x=rank[i][j];if (use[x]) continue;
                if (k<cnt[x]) continue;
                if (fi[n-i][k-cnt[x]]<p){p-=fi[n-i][k-cnt[x]];continue;}
                if (fi[n-i][k-cnt[x]]>=p){
                    k-=cnt[x];ans[i]=x;use[x]=true;
                    for (++x;x<=n;++x) --cnt[x];
                    break;
                }
            }if (j>n){f=true;break;}
        }if (f) printf("-1
");
        else{
            for (i=1;i<n;++i) printf("%d ",ans[i]);
            printf("%d
",ans[n]);
        }
    }
}
View Code

poj1037 A decorative fence

题目大意:求字典序第k的抖动序列。

思路:fi[i][j][0]表示长度为i,以j开头前面下降的个数;fi[i][j][1]表示长度为i,以j开头前面上升的个数。然后像数位dp那样每一次找一下,注意第一位可以上升也可以下降。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxm 25
#define up 20
#define LL long long
using namespace std;
LL fi[maxm][maxm][2]={0};
int ans[maxm]={0},cnt[maxm]={0};
bool vi[maxm]={0};
int main(){
    int i,j,k,n,t,fu;LL m;
    scanf("%d",&t);
    memset(fi,0,sizeof(fi));
    fi[1][1][0]=fi[1][1][1]=1;
    for (i=2;i<=up;++i)
        for (j=1;j<=up;++j){
              for (k=1;k<j;++k) fi[i][j][0]+=fi[i-1][k][1];
              for (k=j;k<i;++k) fi[i][j][1]+=fi[i-1][k][0];
        }
    while(t--){
        scanf("%d%I64d",&n,&m);fu=0;
        for (i=1;i<=n;++i) cnt[i]=i;
        memset(vi,false,sizeof(vi));
        for (i=1;i<=n;++i){
            fu^=1;
            for (j=1;j<=n;++j){
                if (vi[j]) continue;
                if (i==1){
                    if (m-fi[n-i+1][cnt[j]][0]-fi[n-i+1][cnt[j]][1]>0){
                        m-=fi[n-i+1][cnt[j]][0]+fi[n-i+1][cnt[j]][1];
                    }else{
                        if (m-fi[n-i+1][cnt[j]][0]>0){m-=fi[n-i+1][cnt[j]][0];fu=1;}
                        else fu=0;   break;
                    }
                }else{
                    if (fu&&j>=ans[i-1]) continue;
                    if (!fu&&j<=ans[i-1]) continue;
                    if (m-fi[n-i+1][cnt[j]][fu]>0) m-=fi[n-i+1][cnt[j]][fu];
                    else break;
                }
            }if (j<=n){
                ans[i]=j;vi[j]=true;
                for (++j;j<=n;++j) --cnt[j];
            }
        }for (i=1;i<n;++i) printf("%d ",ans[i]);
        printf("%d
",ans[n]);
    }
}
View Code

bzoj3652 bignews(!!!

题目大意:给定n,q(未加密的可能性)。加密的话就是求x(0~n-1)^y(0~n-1)的期望,未加密的就是求x(0~n-1)^y(0~n-1)(y取能使这个最大的值)的期望。

思路:对于加密的求出每一位为1的概率,然后就是sigma2^i*2*x/n*(1-x)/n,对于未加密的,fi[i][j][k]表示到第i位(高位-低位),x、y的限制分别为j、k(0表示之前的小于,1表示之前的卡着上界),然后转移一下,这里用的是上一位的状态求出这一位的状态来更新。其实加密的也可以用未加密的套路来求。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
#define up 70
using namespace std;
int ai[up]={0};
LL mi[up]={0LL},ci[up]={0LL};
double bi[up],fi[up][2][2]={0},gi[up][2][2]={0};
double calc0(LL n){
    int i,j,k,a,b,k1,k2;double ans=0.0,per=1.0/(double)n;
    LL x=n-1;ai[0]=0;
    while(x){
        ai[++ai[0]]=(int)(x%2);x/=2;
    }gi[ai[0]+1][1][1]=per;
    for (i=ai[0];i;--i)
        for (j=0;j<=1;++j)
          for (k=0;k<=1;++k)
              if (gi[i+1][j][k]>0.0)
                  for (a=0;a<=1;++a){
                      if (j){
                          if (a<ai[i]) k1=0;
                          else{
                              if (a==ai[i]) k1=1;
                              else continue;
                          }
                      }else k1=0;
                      if (k){
                          b=a^1;
                          if (b>ai[i]) b^=1;
                          if (b<ai[i]) k2=0;
                          else k2=1;
                      }else{b=a^1;k2=0;}
                      fi[i][k1][k2]+=fi[i+1][j][k]+mi[i-1]*gi[i+1][j][k]*(a^b);
                      gi[i][k1][k2]+=gi[i+1][j][k];
                  }
    for (i=0;i<=1;++i)
      for (j=0;j<=1;++j) ans+=fi[1][i][j];
    return ans;
}
double calc1(LL n){
    int i,j;LL x=n;double ans=0.0;
    for (i=ai[0];i;--i){
        if (ai[i]){
            n-=mi[i-1];ci[i]+=n;
            for (j=i-1;j;--j) ci[j]+=(i-2>=0 ? mi[i-2] : 0LL);
        }
    }for (n=x,i=ai[0];i;--i)
        ans+=(double)ci[i]/(double)n*(double)(n-ci[i])/(double)n*2.0*mi[i-1];
    return ans;
}
int main(){
    LL x,n;int i;double p;
    scanf("%I64d%lf",&n,&p);x=n;
    while(n){
        ai[++ai[0]]=(int)(n%2);n/=2;
    }mi[0]=1LL;n=x;
    for(i=1;i<=ai[0];++i) mi[i]=mi[i-1]*2LL;
    printf("%.6f
",calc0(n)*p+calc1(n)*(1.0-p));
}
View Code

bc73 b

题目大意:求1<=i<j<=n且i的二进制中1个数比j的大的数对个数。

思路:fi[i][j][a][b]表示前i位、数位差是j-N、两数大小关系、大数和n大小关系。枚举转移。初始化的时候fi[0][0+N][1][1]=1。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 1005
#define p 998244353
using namespace std;
char ss[N];
int num[N],len,ai[N],al=0,fi[N][N<<1][2][2]={0};//前i位、位数差j、两数大小、大数和n大小 
bool zero(){return (len==1&&num[1]==0);}
void chu(){
    int i,g=0;
    for (i=len;i;--i){
        g=g*10+num[i];
        num[i]=g/2;g=g%2;
    }while(len>1&&num[len]==0) --len;}
void jia(int &x,int y){x=(x+y)%p;}
int dp(){
    int i,j,a,b,k1,k2,c,d,ans=0;
    for (i=al/2;i;--i) swap(ai[i],ai[al-i+1]);
    memset(fi,0,sizeof(fi));
    fi[0][N][1][1]=1;
    for (i=0;i<al;++i)
      for (j=0;j<=(N<<1);++j)
        for (a=0;a<=1;++a)
          for (b=0;b<=1;++b){
              if (!fi[i][j][a][b]) continue;
              for (k1=0;k1<=1;++k1)
                for (k2=0;k2<=1;++k2){
                    if (!a) c=0;
                    else{
                        if (k1<k2) continue;
                        if (k1==k2) c=1;
                        else c=0;
                    }if (!b) d=0;
                    else{
                        if (k1>ai[i+1]) continue;
                        if (k1==ai[i+1]) d=1;
                        else d=0;
                    }jia(fi[i+1][j+k1-k2][c][d],fi[i][j][a][b]);
                }
          }
    for (j=0;j<N;++j)
      for (b=0;b<=1;++b) jia(ans,fi[al][j][0][b]);
    return ans;}
int main(){
    int i,t;scanf("%d",&t);
    while(t--){
      scanf("%s",ss);len=strlen(ss);al=0;
       for (i=len;i;--i) num[i]=ss[len-i]-'0';
      while(!zero()){
        ai[++al]=num[1]%2;chu();
      }printf("%d
",dp());
    }
}
View Code

bzoj3131 淘金

题目大意:给定一个N*N的网格,每个位置有一个金子,(i,j)->(fi,fj),fx是x各位数乘积。问一次变化后网格中金子最多的k个的和。(N<=10^12)

思路:数位dp,1~N的fi出现几次,因为fi的取值很少,可以用map存下来,暴力dp转移(高位的与原数相等和高位的小于原数)。统计答案相当于已知一个序列,问两两乘积的前k大的和,用堆维护一下。(如果k很大可以二分)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<queue>
#define N 100005
#define up 10
#define LL long long
#define p 1000000007LL
using namespace std;
struct use{
    LL v;int bp,po;
    bool operator<(const use&x)const{return v<x.v;}
};
map<LL,LL>ct1[2],ct2[2];
map<LL,LL>::iterator it,en;
int cur,ai[N],bz=0;
LL bi[N];
priority_queue<use> que;
void pre(LL x){
    int i,j;LL xx=x;ai[0]=0;
    while(xx){
        ai[++ai[0]]=(int)(xx%10LL);
        xx/=10LL;
    }cur=0;
    for (i=ai[ai[0]]-1;i;--i) ct1[cur][(LL)i]=1LL;
    ct2[cur][(LL)ai[ai[0]]]=1LL;++ct1[cur][1LL];
    for (i=ai[0]-1;i;--i){
        cur^=1;ct1[cur].clear();ct2[cur].clear();
        for (en=ct1[cur^1].end(),it=ct1[cur^1].begin();it!=en;++it)
            for (j=1;j<up;++j)
                ct1[cur][(LL)j * it->first]+=it->second;
        for (en=ct2[cur^1].end(),it=ct2[cur^1].begin();it!=en;++it){
            for (j=1;j<ai[i];++j)
                ct1[cur][(LL)j * it->first]+=it->second;
            ct2[cur][(LL)ai[i] * it->first]+=it->second;
        }++ct1[cur][1LL];
    }for (en=ct2[cur].end(),it=ct2[cur].begin();it!=en;++it)
        ct1[cur][it->first]+=it->second;
    --ct1[cur][1];
    for (en=ct1[cur].end(),it=ct1[cur].begin();it!=en;++it){
        if (it->first > x) break;
        if (it->first == 0LL) continue;
        bi[++bz]=it->second;
    }sort(bi+1,bi+bz+1);
    for (i=1;i<=bz;++i)
        que.push((use){bi[i]*bi[bz],i,bz});
}
LL work(int k){
    LL ans=0LL;use cc;k=min(k,bz*bz);
    while(k--){
        cc=que.top();que.pop();
        ans=(ans+cc.v)%p;
        if (cc.po>1) que.push((use){bi[cc.bp]*bi[cc.po-1],cc.bp,cc.po-1});
    }return ans;}
int main(){
    LL n;int k;
    scanf("%I64d%d",&n,&k);
    pre(n);
    printf("%I64d
",work(k));
}
View Code

bzoj3802&&4043 Vocabulary

题目大意:三个字符串,由小写字母和?组成,?可以代表任意一个小写字母,三个长度不一定相同。问三个字典序升序的方案数。

思路:数位dp,fi[i][j]表示长度为i,三个串到这个位置的关系是j的方案数。j=0表示a=b=c;j=1表示a=b<c,j=2表示a<b=c,j=3表示a<b<c的,fi[ml][3]是答案。可以预处理出26的幂,x个数中选2个的、x个数中选3个的组合数,更新的时候根据i位上的字母关系更新。

注意:1)如果递推预处理,要注意更新的方程;

     2)转移的方案很多,很复杂。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 1000005
#define up 3
#define mz 26
#define LL long long
#define p 1000000009LL
using namespace std;
char ss[N];
int li[up],ml=0,ai[up][N];
LL gi[mz+1]={0LL},fi[N][4]={0LL},zi[mz+1]={0LL},mi[mz+1];
int idx(int i){
    int j,u=0;
    for (j=0;j<up;++j)
        if (ai[j][i]<mz) u=u|(1<<j);
    return u;}
inline void add(LL &a,LL b){a=(a+b)%p;}
LL dp(){
    int i,j,u,x,y,z;
    fi[0][0]=1LL;
    for (i=0;i<ml;++i){
        for (u=0,j=0;j<up;++j){
            if (ai[j][i]==N) continue;
            if (u==0) u=ai[j][i]+2;
            else if (u!=ai[j][i]+2) break;
        }if (j>=up){
            if (u==0) add(fi[i+1][0],fi[i][0]*(LL)mz%p);
            else add(fi[i+1][0],fi[i][0]*(LL)(u==1 ? 0 : 1));
        }u=idx(i);
        if (u==0){
            add(fi[i+1][1],(fi[i][0]*zi[mz]%p+fi[i][1]*mi[2]%p)%p);
            add(fi[i+1][2],(fi[i][0]*zi[mz]%p+fi[i][2]*mi[2]%p)%p);
            add(fi[i+1][3],(fi[i][0]*gi[mz]%p+fi[i][1]*zi[mz]*(LL)mz%p+
                fi[i][2]*zi[mz]*(LL)mz%p+fi[i][3]*mi[3]%p)%p);
            continue;
        }if (u==1){
            x=ai[0][i]+1;
            add(fi[i+1][1],(fi[i][0]*(LL)(x?(mz-x):0)%p+fi[i][1]*(LL)(x?mz:0)%p)%p);
            add(fi[i+1][2],(fi[i][0]*(LL)(mz-x)%p+fi[i][2]*(LL)mz%p)%p);
            add(fi[i+1][3],(fi[i][0]*zi[mz-x]%p+fi[i][1]*(LL)mz%p*(LL)(mz-x)%p+
                fi[i][2]*zi[mz]%p+fi[i][3]*mi[2]%p)%p);
            continue;
        }if (u==2){
            x=ai[1][i]+1;
            add(fi[i+1][1],(fi[i][0]*(LL)(x?(mz-x):0)%p+fi[i][1]*(LL)(x?mz:0)%p)%p);
            add(fi[i+1][2],(fi[i][0]*(LL)(x?(x-1):0)%p+fi[i][2]*(LL)(x?mz:0)%p)%p);
            add(fi[i+1][3],(fi[i][0]*(LL)(x?(x-1)*(mz-x):0)%p+fi[i][1]*(LL)(x?(x-1)*mz:0)%p+
                fi[i][2]*(LL)(mz*(mz-x))%p+fi[i][3]*mi[2]%p)%p);
            continue;
        }if (u==4){
            x=ai[2][i]+1;
            add(fi[i+1][1],(fi[i][0]*(LL)(x?(x-1):0)%p+fi[i][1]*(LL)mz%p)%p);
            add(fi[i+1][2],(fi[i][0]*(LL)(x?(x-1):0)%p+fi[i][2]*(LL)(x?mz:0)%p)%p);
            add(fi[i+1][3],(fi[i][0]*(LL)(x?zi[x-1]:0)%p+fi[i][1]*(LL)zi[mz]%p+
                fi[i][2]*(LL)(x?mz*(x-1):0)%p+fi[i][3]*mi[2]%p)%p);
            continue;
        }if (u==3){
            x=ai[0][i]+1;y=ai[1][i]+1;
            if (x==y){
                add(fi[i+1][1],(fi[i][0]*(LL)(mz-x)%p+fi[i][1]*(LL)mz%p)%p);
                add(fi[i+1][2],fi[i][2]*(LL)(x?1:0));
                add(fi[i+1][3],(fi[i][2]*(LL)(mz-x)%p+fi[i][3]*(LL)mz%p)%p);
            }if (x>y){
                add(fi[i+1][2],(fi[i][2]*(LL)(y?1:0)%p));
                add(fi[i+1][3],(fi[i][2]*(LL)(mz-y)%p+fi[i][3]*(LL)mz%p)%p);
            }if (x<y){
                add(fi[i+1][2],(fi[i][0]+fi[i][2])%p);
                add(fi[i+1][3],(fi[i][0]*(LL)(mz-y)%p+fi[i][1]*(LL)mz%p+
                    fi[i][2]*(LL)(mz-y)%p+fi[i][3]*(LL)mz%p)%p);
            }continue;
        }if (u==5){
            x=ai[0][i]+1;y=ai[2][i]+1;
            if (x==y){
                add(fi[i+1][1],fi[i][1]*(LL)(x?1:0));
                add(fi[i+1][2],fi[i][2]*(LL)(x?1:0));
                add(fi[i+1][3],(fi[i][1]*(LL)(mz-x)%p+fi[i][2]*(LL)(x?(x-1):0)%p+
                    fi[i][3]*(LL)mz)%p);
            }if (x>y){
                add(fi[i+1][1],fi[i][1]);
                add(fi[i+1][2],fi[i][2]*(LL)(y?1:0));
                add(fi[i+1][3],(fi[i][1]*(LL)(mz-x)%p+fi[i][2]*(LL)(y?(y-1):0)%p+
                    fi[i][3]*(LL)mz%p)%p);
            }if (x<y){
                add(fi[i+1][1],(fi[i][0]*(LL)(x?1:0)+fi[i][1]*(LL)(x?1:0))%p);
                add(fi[i+1][2],(fi[i][0]+fi[i][2])%p);
                add(fi[i+1][3],(fi[i][0]*(LL)(y-x-1)%p+fi[i][1]*(LL)(mz-x)%p+
                    fi[i][2]*(LL)(y-1)%p+fi[i][3]*mz%p)%p);
            }continue;
        }if (u==6){
            x=ai[1][i]+1;y=ai[2][i]+1;
            if (x==y){
                add(fi[i+1][1],fi[i][1]*(LL)(x?1:0)%p);
                add(fi[i+1][2],fi[i][0]*(LL)(x?(x-1):0)%p+fi[i][2]*(LL)mz%p);
                add(fi[i+1][3],(fi[i][1]*(LL)(x?(x-1):0)%p+fi[i][3]*(LL)mz%p)%p);
            }if (x<y){
                add(fi[i+1][1],(fi[i][0]*(LL)(x?1:0)+fi[i][1]*(LL)(x?1:0))%p);
                add(fi[i+1][3],(fi[i][0]*(LL)(x?(x-1):0)%p+fi[i][1]*(LL)(x?(x-1):0)%p+
                    fi[i][2]*(LL)mz%p+fi[i][3]*mz%p)%p);
            }if (x>y){
                add(fi[i+1][1],fi[i][1]);
                add(fi[i+1][3],(fi[i][1]*(LL)(x-1)%p+fi[i][3]*(LL)mz)%p);
            }continue;
        }if (u==7){
            x=ai[0][i]+1;y=ai[1][i]+1;z=ai[2][i]+1;
            if (x==y&&y<z) add(fi[i+1][1],fi[i][0]);
            if (x==y) add(fi[i+1][1],fi[i][1]);
            if (x<y&&y==z) add(fi[i+1][2],fi[i][0]);
            if (y==z) add(fi[i+1][2],fi[i][2]);
            if (x<y&&y<z) add(fi[i+1][3],fi[i][0]);
            if (x<y) add(fi[i+1][3],fi[i][1]);
            if (y<z) add(fi[i+1][3],fi[i][2]);
            add(fi[i+1][3],fi[i][3]);
        }
    }return fi[ml][3];
}
int main(){
    int i,j,t;scanf("%d",&t);
    for (mi[0]=1LL,i=1;i<=mz;++i){
        zi[i]=(LL)i*(LL)(i-1)/2LL;
        gi[i]=(gi[i-1]+zi[i-1])%p;
        mi[i]=mi[i-1]*(LL)mz%p;
    }while(t--){
        for (i=0;i<=ml;++i)
          for (j=0;j<4;++j) fi[i][j]=0LL;
        for (ml=i=0;i<up;++i){
            scanf("%s",ss);li[i]=strlen(ss);
            for (j=0;j<li[i];++j) ai[i][j]=(ss[j]=='?' ? N : ss[j]-'a');
            ml=max(ml,li[i]);
        }for (i=0;i<up;++i)
            for (j=li[i];j<ml;++j) ai[i][j]=-1;
        printf("%I64d
",dp());
    }
}
View Code

(还可以暴力预处理出所有状态间转移的矩阵,然后转移的时候直接用矩阵就可以了。)

bzoj3209 花神的数论题

题目大意:sum[i]表示i的二进制中1的个数,求∏(i=1~n)sum[i]。(n<=10^15)

思路:数位dp。没有限制的时候,二进制长度小于等于i的贡献是∏(j=1~i)j^c(i,j)。对n的二进制位从高到低考虑,对于是1的这一位,之前都选1,这一位选0的时候算出贡献。最后*sum[n]。

注意:组合数不会爆longlong,但如果要取模的话,因为它是指数,所以应该%phi(p),而这里的p=10000007不是质数,所以要处理。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
#define N 51
#define p 10000007LL
using namespace std;
int ai[N],az=0;
LL c[N][N]={0LL};
LL mi(LL x,LL y){
    LL a=1LL;
    for (;y;y>>=1LL){
        if (y&1LL) a=a*x%p;
        x=x*x%p;
    }return a;}
int main(){
    LL n,bin,ans=1LL;int i,j,cnt=0;
    for (i=0;i<N;++i)
        for (c[i][0]=1LL,j=1;j<=i;++j) c[i][j]=c[i-1][j-1]+c[i-1][j];
    scanf("%I64d",&n);
    for (;n;n>>=1LL) ai[++az]=(int)(n&1LL);
    for (cnt=0;az;--az){
        if (!ai[az]) continue;
        for (i=0;i<az;++i)
            if (cnt+i) ans=ans*mi((LL)(cnt+i),c[az-1][i])%p;
        ++cnt;
    }ans=ans*cnt%p;
    printf("%I64d
",ans);
}
View Code

bzoj2757 Blinker的仰慕者

题目大意:求a~b中各位乘积为k的数的和。

思路:k=0的时候fi[i][j][a][b]表示i位、和x关系j、前面有无非0的数、乘积是不是0的数的个数,ii[][][][]表示和,暴力转移;k!=0的时候这样转移会tle,考虑用最早写过的dp方法,预处理i位长度时乘积为j的个数和和(j的个数十分有限),从高到低卡着dp。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#define N 20
#define p 20120427LL
#define M 4
#define LL long long
#define nn 100000
#define len 1000000
using namespace std;
int pr[M]={2,3,5,7},dt[N]={0};
LL ai[N],di[N][nn],ci[len],fi[N][2][2][2],ii[N][2][2][2],gi[N][nn],hi[N][nn],mi[N];
void add(LL &x,LL y){x=(x+y)%p;}
void pre(){
    int i,j,k,a,cz;LL b;
    memset(gi,0,sizeof(gi));
    memset(hi,0,sizeof(hi));
    di[0][++dt[0]]=1LL;
    gi[0][dt[0]]=1LL;
    for (mi[0]=1LL,i=1;i<N;++i) mi[i]=mi[i-1]*10LL%p;
    for (i=0;i<N-2;++i){
        for (cz=0,j=1;j<=dt[i];++j)
            for (k=1;k<=9;++k) ci[++cz]=di[i][j]*(LL)k;
        sort(ci+1,ci+cz+1);cz=unique(ci+1,ci+cz+1)-ci-1;
        for (dt[i+1]=cz,j=1;j<=cz;++j) di[i+1][j]=ci[j];
        for (j=1;j<=dt[i];++j)
            for (k=1;k<=9;++k){
                b=di[i][j]*(LL)k;
                a=upper_bound(ci+1,ci+cz+1,b)-ci-1;
                add(gi[i+1][a],gi[i][j]);
                add(hi[i+1][a],(gi[i][j]*(LL)k+hi[i][j]*10LL)%p);
            }
    }
}
LL getf(LL x){
    int n,i,j,a,b,c,nj,na,nb;LL k,kk;
    for (n=0;x;x/=10LL) ai[++n]=(int)(x%10LL);
    for (i=1;(i<<1)<=n;++i) swap(ai[i],ai[n-i+1]);
    memset(fi,0,sizeof(fi));
    memset(ii,0,sizeof(ii));
    fi[0][1][0][0]=1LL;
    for (i=0;i<n;++i)
      for (j=0;j<2;++j)
        for (a=0;a<2;++a)
          for (b=0;b<2;++b){
              if (!fi[i][j][a][b]&&!ii[i][j][a][b]) continue;
              k=fi[i][j][a][b];
            kk=ii[i][j][a][b];
            for (c=0;c<=9;++c){
                  nj=0;
                  if (j){
                      if (c>ai[i+1]) continue;
                      if (c==ai[i+1]) nj=1;
                  }na=a;nb=b;
                  if (c) na=1;
                  if (a&&!c) nb=1;
                  add(fi[i+1][nj][na][nb],k);
                  add(ii[i+1][nj][na][nb],(kk*10LL+(LL)c*k)%p);
              }
          }
    return (ii[n][0][1][1]+ii[n][1][1][1])%p;
}
LL geth(int i,LL k){
    int a=upper_bound(di[i]+1,di[i]+dt[i]+1,k)-di[i]-1;
    if (di[i][a]!=k) return 0LL;
    return hi[i][a];}
LL getg(int i,LL k){
    int a=upper_bound(di[i]+1,di[i]+dt[i]+1,k)-di[i]-1;
    if (di[i][a]!=k) return 0LL;
    return gi[i][a];}
LL getg(LL x,LL k){
    int n,i,j;LL ans=0LL,cc=0LL;
    if (!x) return 0;++x;
    for (n=0;x;x/=10LL) ai[++n]=(int)(x%10LL);
    for (i=1;(i<<1)<=n;++i) swap(ai[i],ai[n-i+1]);
    for (i=1;i<n;++i) ans=(ans+geth(i,k))%p;
    for (i=1;i<=n;++i){
        if (!ai[i]) break;
        for (j=1;j<ai[i];++j)
            if (k%(LL)j==0LL){
                ans=(ans+geth(n-i,k/j)+getg(n-i,k/j)*(cc*10LL%p+(LL)j)%p*mi[n-i])%p;
            }
        if (k%(LL)ai[i]) break;
        k/=(LL)ai[i];cc=cc*10LL+ai[i];
    }return ans;}
int main(){
    int i,n;LL a,b,k,kk;
    scanf("%d",&n);pre();
    while(n--){
        scanf("%I64d%I64d%I64d",&a,&b,&k);
        if (!k){printf("%I64d
",((getf(b)-getf(a-1LL))%p+p)%p);continue;}
        for (kk=k,i=0;i<M;++i)
            while(kk%pr[i]==0) kk/=pr[i];
        if (kk>1) printf("0
");
        else printf("%I64d
",((getg(b,k)-getg(a-1LL,k))%p+p)%p);
    }
}
View Code

省队集训 number(!!!

题目大意:给出n,每次可以减去n某个数位上的数字,问n最少几次变为0。

思路:每次都是减去最大的那个数最优,并且肯定是999...i到负数的过程,预处理fi[i][j][k]表示i位数,前i-1位是9,个位数是j,i位之前的数的最大值是k,减到后i位为负数的步数,gi[i][j][k]表示减成的负数。求答案的时候,先从低位到高位消成999...i的形式,再从高位算下来,最后如果个位非0,还要+1。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 20
#define up 10
#define LL long long
using namespace std;
LL fi[N][N][up];
int gi[N][N][up],ai[N]={0};
void pre(){
    int i,j,k,a;
    memset(fi,0,sizeof(fi));
    for (j=0;j<up;++j)
        for (k=0;k<up;++k){
            if (j<k){
                fi[1][j][k]=1LL;
                gi[1][j][k]=10+j-k;
            }else{
                fi[1][j][k]=2LL;
                gi[1][j][k]=10-k;
            }
        }
    for (i=2;i<N;++i){
        for (j=0;j<up;++j)
            for (k=0;k<up;++k){
                gi[i][j][k]=j;
                for (a=up-1;a>=0;--a){
                    fi[i][j][k]+=fi[i-1][gi[i][j][k]][max(a,k)];
                    gi[i][j][k]=gi[i-1][gi[i][j][k]][max(a,k)];
                }
            }
    }
}
LL dp(LL n){
    int i,j,ci,cc;LL ans=0LL;
    for (;n;n/=10LL) ai[++ai[0]]=(int)(n%10LL);
    ci=ai[1];
    for (i=2;i<=ai[0];++i)
        while(ai[i]!=9){
            for (cc=0,j=i;j<=ai[0];++j) cc=max(cc,ai[j]);
            if (!cc) break;
            ans+=fi[i-1][ci][cc];
            ci=gi[i-1][ci][cc];
            --ai[i];
            for (j=i;ai[j]<0;++j){ai[j]+=10;--ai[j+1];}
        }
    for (i=ai[0];i>=2;--i){
        while(ai[i]){
            ans+=fi[i-1][ci][ai[i]];
            ci=gi[i-1][ci][ai[i]];
            --ai[i];
        }
    }return ans+(ci!=0);
}
int main(){
    freopen("number.in","r",stdin);
    freopen("number.out","w",stdout);
    
    LL n;pre();
    scanf("%I64d",&n);
    printf("%I64d
",dp(n));
}
View Code

poj3899 The Lucky Numbers

题目大意:认为只包含4和7的数字是幸运数字,求[a,b]内的幸运数字的个数+x是幸运数字且x在[a,b]但x翻转过来不再[a,b]内的个数(翻转,如:447变为744)。

思路:进行三次数位dp,分别求出[a,b]内幸运数字个数、x在[a,b]内翻转后在[b+1,)内、x在[a,b]内翻转后在[0,a-1]内。可以用上面的数位dp,相应表示大小关系,求幸运数字的时候可能包含前导0,其他的两个因为翻转和范围的问题所以没有前导0。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 50
#define up 10
using namespace std;
struct use{
    int l,nm[N];
    void clear(){memset(nm,0,sizeof(nm));l=1;}
    bool zero(){return (l==1&&nm[1]==0);}
    use operator+(const use&x)const{
        int a,b,i,g=0;use c;c.l=max(l,x.l);
        for (i=1;i<=c.l;++i){
            a=(i>l ? 0 : nm[i]);
            b=(i>x.l ? 0 : x.nm[i]);
            c.nm[i]=a+b+g;
            g=c.nm[i]/10;
            c.nm[i]%=10;
        }while(g){c.nm[++c.l]=g%10;g/=10;}
        return c;
    }
    use operator-(const use&x)const{
        int b,i;use c;c.l=l;c.nm[1]=0;
        for (i=1;i<=c.l;++i){
            b=(i>x.l ? 0 : x.nm[i]);
            c.nm[i]=c.nm[i]+nm[i]-b;
            if (c.nm[i]<0){
                c.nm[i]+=10;
                c.nm[i+1]=-1;
            }else c.nm[i+1]=0;
        }while(c.l>1&&!c.nm[c.l]) --c.l;
        return c;
    }
}fi[N][2][2],ai,bi,per;
char ss[N];
use in(){
    int i;use ci;scanf("%s",ss);
    ci.l=strlen(ss);
    for (i=ci.l;i;--i) ci.nm[i]=ss[ci.l-i]-'0';
    return ci;}
void add(use &x,use y){x=x+y;}
void print(use x){
    for (int i=x.l;i;--i) printf("%d",x.nm[i]);
    printf("
");
}
use dp(use x){
    int i,j,a,b,na,nb,xx;
    for (i=0;i<N;++i)
        for (a=0;a<2;++a)
            for (b=0;b<2;++b) fi[i][a][b].clear();
    fi[0][1][0].nm[1]=1;
    for (i=0;i<x.l;++i){
        xx=x.nm[x.l-i];
        for (a=0;a<2;++a)
            for (b=0;b<2;++b){
                if (fi[i][a][b].zero()) continue;
                for (j=0;j<up;++j){
                    if (j&&j!=4&&j!=7) continue;
                    if (a){
                        if (j>xx) continue;
                        na=(j==xx);
                    }else na=0;
                    if (!j&&b) continue;
                    nb=(j>0);
                    add(fi[i+1][na][nb],fi[i][a][b]);
                }
            }
    }return fi[x.l][0][1]+fi[x.l][1][1];
}
use cal(use l,use r){
    int ll,rr,i,j,a,b,na,nb;
    for (i=0;i<N;++i)
        for (a=0;a<2;++a)
            for (b=0;b<2;++b) fi[i][a][b].clear();
    fi[0][1][0].nm[1]=1;
    for (i=0;i<r.l;++i){
        ll=(r.l-i>l.l ? 0 : l.nm[r.l-i]);
        rr=r.nm[i+1];
        for (a=0;a<2;++a)
            for (b=0;b<2;++b){
                if (fi[i][a][b].zero()) continue;
                for (j=0;j<up;++j){
                    if (j!=4&&j!=7) continue;
                    if (a){
                        if (j>ll) continue;
                        na=(j==ll);
                    }else na=0;
                    if (j<rr) nb=0;
                    if (j==rr) nb=b;
                    if (j>rr) nb=1;
                    add(fi[i+1][na][nb],fi[i][a][b]);
                }
            }
    }return fi[r.l][0][1]+fi[r.l][1][1];
}
use dp2(use l,use r){
    int ll,rr,i,j,a,b,na,nb;
    for (i=0;i<N;++i)
        for (a=0;a<2;++a)
            for (b=0;b<2;++b) fi[i][a][b].clear();
    if (l.l>r.l) fi[0][0][1].nm[1]=1;
    else fi[0][1][1].nm[1]=1;
    for (i=0;i<r.l;++i){
        ll=l.nm[r.l-i];
        rr=r.nm[i+1];
        for (a=0;a<2;++a)
            for (b=0;b<2;++b){
                if (fi[i][a][b].zero()) continue;
                for (j=0;j<up;++j){
                    if (j!=4&&j!=7) continue;
                    if (a){
                        if (j>ll) continue;
                        na=(j==ll);
                    }else na=0;
                    if (j<rr) nb=0;
                    if (j==rr) nb=b;
                    if (j>rr) nb=1;
                    add(fi[i+1][na][nb],fi[i][a][b]);
                }
            }
    }return fi[r.l][0][0]+fi[r.l][1][0];
}
int main(){
    int t;scanf("%d",&t);
    per.l=per.nm[1]=1;
    while(t--){
        ai=in();bi=in();
        print(dp(bi)-dp(ai-per)+cal(bi,bi)-cal(ai-per,bi)+dp2(bi,ai-per)-dp2(ai-per,ai-per));
    }
}
View Code
原文地址:https://www.cnblogs.com/Rivendell/p/4686991.html