有一些题之前已经写了题解了,就只留一个链接吧…
一般的数位DP都是计算一段区间满足某条件的数有多少个。
顾名思义数位DP就是按照数一位一位滴进行DP。通常至少有二维,其中一位表示当前在第 i 位上,另一维表示与 n 的大小关系。
具体实现方法通常有递推版和记忆化搜索版。
SPOJ10606
BZOJ3629
CodeForces-55D
题目大意:题目大意:给定 L,R ,求 [L,R] 中所有可以被它所有非零数位整除的数的个数。
比如说 52 就是一个满足题意的数。
由于被非零数位整除这一个条件很讨厌,尝试将所有可能的数位转化成同一个数整除。这个数就是 1 到 9 的数的最小公倍数 2520 。只要最后再判断一下就行了。
然后我们发现 f[20][2525][2525] 好像有一点点存不下…
然后 1 到 9 中任意一个集合的 lcm 只有 49 种。所有把他们离散化一下。
#include <iostream>
#include <cstdio>
#include <cstring>
#define LL long long int
#define mod 2520
using namespace std;
LL f[20][mod+5][50];
int h[mod+5], w[20], len, cnt;
int gcd(int a,int b)
{
if(!b)return a;
return gcd(b,a%b);
}
int lcm(int a,int b)
{
if(!b)return a;
return a*b/gcd(a,b);
}
LL dfs(int i,int l,int rest,bool flag)
{
if(i==0)return rest%l==0;
if(!flag&&~f[i][rest][h[l]])return f[i][rest][h[l]];
int j;
LL ans=0;
if(flag)j=w[i];
else j=9;
for(;j>=0;--j)
ans+=dfs(i-1,lcm(l,j),(rest*10+j)%mod,flag&&j==w[i]);
if(!flag)f[i][rest][h[l]]=ans;
return ans;
}
//可能有人会问:为什么在计算时f数组不清零?
//其实木有必要清零啊,每一次都是从1到9,每一次算的答案都会是一样的
LL cal(LL n)
{
if(n==0)return 1;
len=0;
while(n){w[++len]=n%10;n/=10;}
return dfs(len,1,0,1);
}
void init()
{
memset(f,-1,sizeof f);
for(int i=1;i<=mod;++i)
if(mod%i==0)
h[i]=++cnt;
}
int main()
{
init();
int cas;
LL l, r;
scanf("%d",&cas);
while(cas--)
{
scanf("%I64d%I64d",&l,&r);
printf("%I64d\n",cal(r)-cal(l-1));
}
return 0;
}
BZOJ3329
这样的 x 满足什么性质?
将 x 写成二进制不存在两个相邻的 1 。
然后令 f[i][j][k] 表示当前算到第 i 位(二进制),第 i 位上填的数是 j ,与 n 的大小关系为 k 。
于是乎第一问解决。
第二问:
令 g[i][j] 表示第 i 为 j ,那么有:
f[i][0]=f[i−1][0]+f[i−1][1],f[i][1]=f[i−1][0]
然后把两个式子合并一下就有: f[i]=f[i−1]+f[i−2]
这不是Fibonacci sequence?
矩阵加速上吧…
#include <iostream>
#include <cstdio>
#include <cstring>
#define LL long long int
#define mod 1000000007
using namespace std;
int len, w[65];
struct mat
{
LL num[5][5];
void init(){memset(num,0,sizeof num);}
mat operator * (const mat &a)const
{
mat ans;
ans.init();
for(int i=1;i<=2;++i)
for(int j=1;j<=2;++j)
for(int k=1;k<=2;++k)
ans.num[i][j]=(ans.num[i][j]+num[i][k]*a.num[k][j])%mod;
return ans;
}
}a, b;
mat power(mat a,LL pos)
{
mat ans=a;
while(pos)
{
if(pos&1)ans=ans*a;
a=a*a;
pos>>=1;
}
return ans;
}
LL f[65][2][2], n;
LL solve1(LL n)
{
if(n==0)return 0;
len=0;
while(n){w[++len]=n&1;n>>=1;}
memset(f,0,sizeof f);
f[len][0][0]=1, f[len][1][1]=1;
for(int i=len-1;i;--i)
{
for(int j=0;j<2;++j)
{
for(int k=0;k<2;++k)
if(!k||!j)
{
if(w[i]>k)f[i][k][0]+=f[i+1][j][1];
else if(w[i]==k)f[i][k][1]+=f[i+1][j][1];
f[i][k][0]+=f[i+1][j][0];
}
}
}
return f[1][0][1]+f[1][0][0]+f[1][1][0]+f[1][1][1]-1;
}
LL solve2(LL n)
{
b.num[1][1]=b.num[1][2]=1;
b=b*power(a,n-1);
return b.num[1][2];
}
int main()
{
a.num[1][2]=a.num[2][1]=a.num[2][2]=1;
int cas;
scanf("%d",&cas);
while(cas--)
{
scanf("%lld",&n);
printf("%lld\n%lld\n",solve1(n),solve2(n));
}
return 0;
}
HDU4352
题目大意:定义一个数字的 val 为将其转化为字符串,最长上升子序列的长度即是 val 。求 [L,R] 中 val=k 的数的个数。
考虑最长上升子序列的 O(nlogn) 的做法。
维护一个类似于单调栈的东西,插入元素 i 时,用元素 i 替换最小的且大于 i 的那个数(若元素 i 是最大的,直接加在最后)。栈的长度就是最长上升子序列长度。
令 f[i][s][k] 表示计算到第 i 位,在单调栈中元素出现情况为 s ,与 n 的大小关系为 k 。
然后用记忆化搜索吧,还不能理解的就看代码和注释吧…
#include <iostream>
#include <cstdio>
#include <cstring>
#define LL long long int
using namespace std;
LL l, r, k, f[25][2048][11];
int len, w[25];
//单调栈中插入元素的操作,删除某个数的标记,添加元素j的标记
int get(int s,int j)
{
for(int l=j;l<10;++l)
if(s&(1<<l)){s^=1<<l;break;}
s|=1<<j;
return s;
}
//is_zero用来统计前导零的
LL dfs(int pos,int s,bool flag,bool is_zero)
{
if(pos==0)
{
int cnt=0;
//统计有多少个数在单调栈中出现了
while(s&&cnt<=k)
{
if(s&1)++cnt;
s>>=1;
}
return cnt==k;
}
if(!flag&&~f[pos][s][k])return f[pos][s][k];
LL ans=0;
int j;
if(flag)j=w[pos];
else j=9;
for(l;j>=0;--j)
if(is_zero&&j==0)
ans+=dfs(pos-1,0,flag&&j==w[pos],1);
else ans+=dfs(pos-1,get(s,j),flag&&j==w[pos],0);
if(!flag)f[pos][s][k]=ans;
return ans;
}
LL cal(LL n)
{
if(n==0)return 0;
len=0;
while(n){w[++len]=n%10;n/=10;}
return dfs(len,0,1,1);
}
int main()
{
memset(f,-1,sizeof f);
int cas=0, t;
scanf("%d",&t);
while(t--)
{
scanf("%I64d%I64d%I64d",&l,&r,&k);
printf("Case #%d: %I64d\n",++cas,cal(r)-cal(l-1));
}
return 0;
}
SGU390
题目大意:有一位售票员给乘客售票,对于每位乘客,他会卖出多张连续的票,直到已卖出的编号的所有位置上的数的和不小于给定的正数 k 。然后他会按照相同的规则给下一位乘客售票。初始时,售票员持有的编号是从 L 到 R 的连续整数。请你求出,售票员可以售票给多少位乘客。
首先此题和上面的题不一样的地方在于不能直接计算 cal(n) 然后输出 cal(r)−cal(l−1) 。要同时计算 cal(l,r) 。
把 f 数组弄成一个pair类型吧。
first 表示票数, second 表示最终剩余容量。
然后枚举第 i 位上的数,更新一下票数和剩余容量。
#include <iostream>
#include <cstdio>
#define LL long long int
using namespace std;
LL l, r;
int k, w[20], len, w2[20], len2;
struct node
{
LL cnt, rem;
node(){cnt=-1, rem=0;}
node(const LL &a,const LL &b){cnt=a, rem=b;}
void operator += (const node &a){
cnt+=a.cnt;
rem=a.rem;
}
}dp[19][205][1005];
//sum表示目前的数位之和,rem表示剩余容量
node dfs(int pos,LL sum,LL rem,bool f,bool f2)
{
if(!pos)
{
if(sum+rem>=k)return node(1,0);
return node(0,sum+rem);
}
if(!f&&!f2&&~dp[pos][sum][rem].cnt)return dp[pos][sum][rem];
node ans(0,rem);
int e, j;
if(f)j=w[pos];
else j=0;
if(f2)e=w2[pos];
else e=9;
for(;j<=e;++j)
ans+=dfs(pos-1,sum+j,ans.rem,f&&j==w[pos],f2&&j==w2[pos]);
if(!f&&!f2)dp[pos][sum][rem]=ans;
return ans;
}
LL cal(LL l,LL r)
{
len=0;
while(l){w[++len]=l%10;l/=10;}
len2=0;
while(r){w2[++len2]=r%10;r/=10;}
for(int i=len+1;i<=len2;++i)w[i]=0;
return dfs(len2,0,0,1,1).cnt;
}
int main()
{
scanf("%I64d%I64d%d",&l,&r,&k);
printf("%I64d\n",cal(l,r));
return 0;
}
ZOJ2599
HDU5519
题目大意:给定 a0,a1,a2,a3,a4 以及 n ,问有多少不含前导 0 的 5 进制 n 位数满足数字 i 的个数不超过 ai 。
不含前导零太可恨辣,于是乎令 w(n,a0,a1,a2,a3,a4) 表示个数不超过 ai 位数为 n 的数的个数。(其实这个函数对后面的编码木有作用= =)
那么 ans=w(n,a0,a1,a2,a3,a4)−w(n−1,a0−1,a1,a2,a3,a4)
为了计算 w ,令 dp[i][s] 表示计算到第 i 位,哪些数字使用过的用 s 来压位表示。
有:
f[i][s]+=f[i−1][s]∗cnt[s];
解释:对于出现过的数字都可以再出现一次。
f[i][s]+=f[i−1−aj][s‘]∗C(i−1,aj)
解释:对于一个没出现的数 j ,将它全部插入。
#include <iostream>
#include <cstdio>
#include <cstring>
#define mod 1000000007
#define MAXN 15005
#define LL long long int
using namespace std;
const int END=1<<5;
int n, dp[MAXN][END+5], a[10];
LL fac[MAXN], ni[MAXN], cnt[END+5];
void init()
{
fac[0]=fac[1]=ni[0]=ni[1]=1;
for(int i=2;i<=15000;++i)
{
fac[i]=fac[i-1]*i%mod;
ni[i]=-mod/i*ni[mod%i]%mod;
if(ni[i]<0)ni[i]+=mod;
}
for(int i=2;i<=15000;++i)ni[i]=ni[i]*ni[i-1]%mod;
//cnt[i]表示i中有几个1
for(int i=1;i<END;++i)cnt[i]=cnt[i^(i&(-i))]+1;
}
LL c(int n,int m){return fac[n]*ni[m]%mod*ni[n-m]%mod;}
int solve()
{
memset(dp,0,sizeof dp);
for(int s=0;s<END;++s)
if(cnt[s^(END-1)]&1)dp[0][s]=mod-1;
else dp[0][s]=1;
for(int i=1;i<=n;++i)
{
for(int s=0;s<END;++s)
{
dp[i][s]=(dp[i][s]+dp[i-1][s]*cnt[s]%mod)%mod;
for(int k=0;k<5;++k)
if(!((s>>k)&1)&&a[k]<i)
dp[i][s|(1<<k)]=(dp[i][s|(1<<k)]+dp[i-1-a[k]][s]*c(i-1,a[k]))%mod;
}
}
return dp[n][END-1];
}
int work()
{
int ans=solve();
if(a[0]>0)
{
--n, --a[0];
ans=(ans-solve()+mod)%mod;
}
return ans;
}
int main()
{
init();
int t, cas=0;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i=0;i<5;++i)scanf("%d",&a[i]);
printf("Case #%d: %d\n",++cas,work());
}
return 0;
}