简单题
写的第一个,非递归版。感觉非递归的数位Dp比较巧妙,也还是比较套路
先预处理,然后统计比最高位位数小的树的答案,统计最高位位数的答案时,先统计不到lim的,再往下一层统计lim的
//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
typedef long long LL;
int lim[20];
LL l,r,power=1,dp[20][20][20],ans[2][10];
void pre(){
for(int i=0;i<10;i++)
dp[1][i][i]=1;
for(int i=2;i<=13;i++){
power*=10;
for(int j=0;j<10;j++){
dp[i][j][j]+=power;
for(int k=0;k<10;k++)
for(int l=0;l<10;l++)
dp[i][j][l]+=dp[i-1][k][l];
}
}
}
void calu(LL x,int p){
int k=0;
if(!x) lim[++k]=0;
while(x){
lim[++k]=x%10;
x/=10;
}
LL power=1,cnt=1;
for(int i=1;i<=k;i++){
ans[p][lim[i]]+=cnt;
cnt+=lim[i]*power;
power*=10;
for(int j=(i==k&&i!=1);j<lim[i];j++)
for(int kk=0;kk<10;kk++)
ans[p][kk]+=dp[i][j][kk];
for(int j=(i!=2);j<10;j++)
for(int kk=0;kk<10;kk++)
ans[p][kk]+=dp[i-1][j][kk];
}
}
int main()
{
pre();
scanf("%lld%lld",&l,&r);
if(l>=1) calu(l-1,0);
calu(r,1);
for(int i=0;i<10;i++){
if(i) putchar(' ');
printf("%lld",ans[1][i]-ans[0][i]);
}
return 0;
}
-------------19-3-28upd------------------
已经不会写数位dp了,自己yy着写了个,调了好久。。
//Achen #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<vector> #include<cstdio> #include<queue> #include<cmath> #define For(i,a,b) for(int i=(a);i<=(b);i++) #define Rep(i,a,b) for(int i=(a);i>=(b);i--) const int N=14; typedef long long LL; typedef double db; using namespace std; LL l,r,dp[N][N][N],ans[N],power[N]; int lim[N]; template<typename T>void read(T &x) { char ch=getchar(); x=0; T f=1; while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar(); if(ch=='-') f=-1,ch=getchar(); for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f; } void pre() { power[1]=1; For(i,0,9) dp[1][i][i]=1; For(i,2,13) { power[i]=power[i-1]*10; For(j,0,9) { For(k,0,9) For(l,0,9) dp[i][j][l]+=dp[i-1][k][l]; dp[i][j][j]+=power[i]; } } } void solve(LL n,int f) { LL tp=n,tpp=n; lim[0]=0; if(n>=0) ans[0]+=f; if(!n) return; while(tp) { lim[++lim[0]]=tp%10; tp/=10; } For(i,1,lim[0]-1) For(j,1,9) For(k,0,9) ans[k]+=f*dp[i][j][k]; Rep(i,lim[0],1) { tpp=tpp%power[i]; int dn=(i==lim[0]),up=lim[i]; For(j,0,9) For(k,0,9) ans[k]+=f*(up-dn)*dp[i-1][j][k]; For(j,dn,up-1) ans[j]+=f*power[i]; ans[lim[i]]+=f*(tpp+1); } } int main() { #ifdef DEBUG freopen(".in","r",stdin); freopen(".out","w",stdout); #endif read(l); read(r); pre(); solve(l-1,-1); solve(r,1); For(i,0,9) printf("%lld ",ans[i]); return 0; }
套路跟上一题差不多,几乎抄的学长的标程感觉比较坑,对1的处理有问题,但问题不大这道题数据可以过。
//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int maxn=2000000000+299;
const int maxl=12;
int l,r,dp[maxl][20],base[maxl];
void pre(){
for(int i=0;i<10;i++) dp[1][i]=1;
for(int i=2;i<maxl;i++)
for(int j=0;j<10;j++)
for(int k=0;k<10;k++)
if(abs(j-k)>=2) dp[i][j]+=dp[i-1][k];
base[1]=1;
for(int i=2;i<maxl;i++) base[i]=base[i-1]*10;
}
int cal(int x){
int res=0;
int w=10;
if(!x) return 0;
while(base[w]>x) w--;
for(int i=1;i<w;i++)
for(int j=1;j<10;j++)
res+=dp[i][j];
int cur,pre;
pre=x/base[w];
for(int i=1;i<pre;i++)
res+=dp[w][i];
x%=base[w];
for(int i=w-1;i;i--){
cur=x/base[i];
x%=base[i];
for(int j=0;j<cur;j++)
if(abs(pre-j)>=2) res+=dp[i][j];
if(i==1){
int j=cur;
if(abs(pre-j)>=2)res+=dp[i][cur];}
if(abs(pre-cur)<2) break;
pre=cur;
}
return res;
}
int main()
{
pre();
scanf("%d%d",&l,&r);
printf("%d",cal(r)-cal(l-1));
return 0;
}
对于我这种ZZ来说有点难理解
我们考虑1出现了k次的数有多少个,就可以用组合数来做。
预处理出组合数,然后从高位到低位扫,依照前两题的套路走
for(int i=(cnt==0);i<top;i++)
表示最高位是0的答案,然后往后计算最高位是1的答案
cnt表示前面已经有了多少个1,这些都是统计达到lim的答案
然后用组合数,快速幂。
//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
typedef long long LL;
const int mod=10000007;
LL cnt,n,C[70][70],ans=1;
int w[70],top;
using namespace std;
LL ksm(LL a,LL b){
LL base=a,res=1;
while(b){
if(b&1) (res*=base)%=mod;
(base*=base)%=mod;
b>>=1;
}
return res;
}
int main()
{
scanf("%lld",&n);
for(;n;n>>=1) w[++top]=n&1;
for(int i=0;i<=65;i++) C[i][0]=1;
for(int i=1;i<=65;i++)
for(int j=1;j<=i;j++)
C[i][j]=C[i-1][j]+C[i-1][j-1];
for(;top;top--) if(w[top]){
for(int i=(cnt==0);i<top;i++)
(ans*=ksm((cnt+i),C[top-1][i]))%=mod;
cnt++;
}
printf("%lld\n",(ans*cnt)%mod);
return 0;
}
一道一看就知道怎么转移的简单题,但是用dp就。。非常难写,自己傻逼地写了个98行的WA了,网上找了一个九个for套8个if的,简直可怕。
而用记忆化搜索就非常方便了。
//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
typedef long long LL;
LL l,r,ansl,ansr;
int lim[20],dp[20][20][20][20][20];
LL dfs(int pos,int now,int fo,int eig,int cnt,int limit){
if(!pos) return cnt==2;
if(!limit&&dp[pos][now][fo][eig][cnt]) return dp[pos][now][fo][eig][cnt];
int up=limit?lim[pos]:9;
LL res=0;
for(int i=(pos==11);i<=up;i++){
if((eig&&i==4)||(fo&&i==8)) continue;
if(cnt==2) res+=dfs(pos-1,i,fo||i==4,eig||i==8,cnt,limit&&i==lim[pos]);
else
res+=dfs(pos-1,i,fo||i==4,eig||i==8,(i==now?cnt+1:0),limit&&i==lim[pos]);
}
if(!limit) dp[pos][now][fo][eig][cnt]=res;
return res;
}
void cal(LL x,LL &ans){
LL tp=x;
for(int i=1;i<=11;i++){
lim[i]=tp%10;
tp/=10;
}
ans=dfs(11,0,0,0,0,1);
}
int main()
{
scanf("%lld%lld",&l,&r);
if(l!=10000000000) cal(l-1,ansl);
cal(r,ansr);
printf("%lld\n",ansr-ansl);
return 0;
}
套路 :
如果搜到底了就返回是否满足要求。
否则,若是没有到limit且已经搜过了,就返回记忆化的值。
否则就重新搜一遍。
搜完返回值,若是不是Limit就把这个结果记忆化。
很套路的数位DP
关键在于预处理出所有可能的乘积,爆搜是9^18好像,反正几年跑出来(SXY大佬帮我算的这个)
根据唯一分解定理,因为所有数都是1~9,所以只要枚举2^a*3^b*5^c*7^d就可以预处理出来了。
//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
#include<set>
using namespace std;
typedef long long LL;
const int maxn=1e9;
LL tp,n,a[6000],dp[20][6000],sz,l,r,ansl,ansr;
int lim[20],cnt;
void pre(){
for(int i=0;i<=30;i++)
for(int j=0;j<=19;j++)
for(int k=0;k<=13;k++)
for(int l=0;l<=11;l++){
LL res=pow(2,i)*pow(3,j)*pow(5,k)*pow(7,l);
if(res>0&&res<=n) a[++sz]=res;
}
sort(a+1,a+sz+1);
}
LL dfs(int pos,int sum,int limit,int flag){
if(!pos) return 1;
if(!limit&&~dp[pos][sum]) return dp[pos][sum];
int up=limit?lim[pos]:9;
LL res=0;
for(int i=(!flag||(flag&&pos==1));i<=up;i++){
LL realsum=a[sum];
if(flag) realsum=1;
if(realsum*i>n) break;
if(realsum*i<=n){
int now=lower_bound(a,a+sz+1,realsum*i)-a;
res+=dfs(pos-1,now,limit&&i==lim[pos],flag&&i==0);
}
}
if(!limit) dp[pos][sum]=res;
return res;
}
void cal(LL x,LL &ans){
if(!x) {return;}
memset(dp,-1,sizeof(dp));
cnt=0; tp=x;
while(tp) {lim[++cnt]=tp%10; tp/=10;}
ans+=dfs(cnt,1,1,1);
}
int main()
{
scanf("%lld%lld%lld",&n,&l,&r);
pre();
cal(l-1,ansl);
cal(r-1,ansr);
printf("%lld\n",ansr-ansl);
return 0;
}
要求模所有位的数字之和为0的数的个数,因为所有和只有162,所以可以枚举这个模数,然后记忆化搜索
dp[i][j][k]表示在当前模数下,长度为i,数字和为j,数字模模数为k的数的个数
//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int maxn=18*9;
typedef long long LL;
int lim[20],n,mod;
LL ans,ansl,ansr,l,r,dp[20][maxn][maxn];
LL dfs(int pos,int sum,int val,int limit){
if(sum>mod) return 0;
if(!pos) return (sum==mod&&val==0);
if(!limit&&~dp[pos][mod-sum][val]) return dp[pos][mod-sum][val];
int li=limit?lim[pos]:9;
LL res=0;
for(int i=0;i<=li;i++)
res+=dfs(pos-1,sum+i,(val*10+i)%mod,(limit&&i==li));
if(!limit) dp[pos][mod-sum][val]=res;
return res;
}
void cal(LL x){
if(!x) return ; LL tp=x; n=0;
while(tp) {lim[++n]=tp%10;tp/=10;}
for(int i=1;i<=n*9;i++){
if(i>x) break;
memset(dp,-1,sizeof(dp));
mod=i;
ans+=dfs(n,0,0,1);
}
}
int main()
{
scanf("%lld%lld",&l,&r);
cal(r);
ansr=ans; ans=0;
cal(l-1);
ansl=ans;
printf("%lld\n",ansr-ansl);
return 0;
}
7.邱老师选妹子
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
int l,r,dp[8][2],ans,t,dig[8],vis[8][2];
int dfs(int len,int sta,bool flag)
{
if(len==0)return 1;
if(!flag&&vis[len][sta]) return dp[len][sta];
int ans=0,up=flag?dig[len]:9;
for(int i=0;i<=up;i++)
{
if(i==4||(sta&&i==2))continue;
ans+=dfs(len-1,i==6?1:0,flag&&i==up);
}
if(!flag){vis[len][sta]=1;dp[len][sta]=ans;}
return ans;
}
int get(int n)
{
int t=0;
while(n)
{
dig[++t]=n%10;
n/=10;
}
return dfs(t,0,true);
}
int main()
{
while(cin>>l>>r)
{
if(l==0&&r==0)break;
cout<<get(r)-get(l-1)<<endl;
}
return 0;
}
一道智障级的数位dp
中等题
数位dp+ac自动机
较难题
2.bzoj3598: [Scoi2014]方伯伯的商场之旅