zoukankan      html  css  js  c++  java
  • 数位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
  • 相关阅读:
    idea语法检查红线隐藏配置
    spring security
    linux centos7下安装fastdfs
    定时任务在多个服务实例之间最多只执行一次
    C++11:01auto关键字
    chap3 数组 #C
    django之模型层 各种查询 数据库查询优化相关 事务使用
    django orm 中表与表之间建关系 视图层 路由层 django请求生命周期
    django 静态文件的配置 orm 中 字段与数据的增删改查 使用MySQL数据库
    BOM,DOM, JS,JQ
  • 原文地址:https://www.cnblogs.com/Rivendell/p/4686991.html
Copyright © 2011-2022 走看看