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\n",work(r,(LL)n)-work(l,(LL)n));
}
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\n",calc(b)-calc(a-1));
}
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\n",ans[9]);
}
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\n",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\n",((calc(bi)-calc(ai))%p+p)%p);
}
}
}
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\n");
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\n");
else{
for (i=1;i<n;++i) printf("%d ",ans[i]);
printf("%d\n",ans[n]);
}
}
}
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\n",ans[n]);
}
}
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\n",calc0(n)*p+calc1(n)*(1.0-p));
}
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\n",dp());
}
}
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\n",work(k));
}
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\n",dp());
}
}
(还可以暴力预处理出所有状态间转移的矩阵,然后转移的时候直接用矩阵就可以了。)
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\n",ans);
}
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\n",((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\n");
else printf("%I64d\n",((getg(b,k)-getg(a-1LL,k))%p+p)%p);
}
}
省队集训 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\n",dp(n));
}
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("\n");
}
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));
}
}
来源:https://www.cnblogs.com/Rivendell/p/4686991.html
