@xiaoziyao
2020-06-12T22:29:49.000000Z
字数 4286
阅读 1148
解题报告
P3107 [USACO14OPEN]Odometer S解题报告:
题意:给定区间,求区间至少一半的数位相同的数数量。
数据范围:。
数位dp的普通题,但是有些细节需要注意。
这里,我会把我所有的思考过程记录下来:
观察题面,发现这道题很显然是数位dp。
我们发现处理区间至少一半的数位相同的数的数量不好做,那么就把它拆成处理每个数字出现超过一半的数的数量。
我们可以先写出函数的板子(即拆解数位):
int calc(int x,int k){
memset(f,-1,sizeof(f));
for(len=0;x;x/=10)
num[++len]=x%10;
return dfs1(...);
}
这里指拆解的数位为数组,并求小于等于,且数字不少于一半的数的个数的函数。
然后开始写记忆化搜索的函数,考虑有哪些值可以影响答案:
这样,函数的的参数就出来了:int dfs1(int len,int k,int cnt1,int cnt2,int flg1,int flg2)
然后是记忆化搜索的套路:用数组表示处理长度为,当前钦定的数出现次数出现次,非当前钦定的数出现次,卡位标志为,前导零标志为的答案。
考虑转移也很简单:枚举数位,然后对所有可以放得下(或)的方案进行转移,注意在与进行转移的时候需要判断前导零:
即:res+=dfs1(len-1,k,cnt1+(i!=0||flg2==0)*(i==k),cnt2+(i!=0||flg2==0)*(i!=k),i==num[len]&&flg1,i==0&&flg2);
这样,函数也出来了:
int dfs(int len,int k,int cnt1,int cnt2,int flg1,int flg2){
if(len==0)
return cnt1>=cnt2;
if(f[len][cnt1][cnt2][flg1][flg2]!=-1)
return f[len][cnt1][cnt2][flg1][flg2];
int res=0;
for(int i=0;i<=9;i++)
if(i<=num[len]||flg1==0)
res+=dfs1(len-1,k,cnt1+(i!=0||flg2==0)*(i==k),cnt2+(i!=0||flg2==0)*(i!=k),i==num[len]&&flg1,i==0&&flg2);
return f[len][cnt1][cnt2][flg1][flg2]=res;
}
虽然这样能过样例,但是交上去后会收获WA的好成绩。
这个程序有两点错误:
第二点是为什么呢?我们举一个例子:,这个数可以在时产生贡献,也会在时产生贡献,即一个长度为偶数的数有可能会出现两个数出现次数同时不少于一半。
此时我们可以使用容斥,将总数减去这些特殊的数,在这里为了方便思考,我把这种特殊的数的求解用了另一个函数,当然也用了另一个记忆化搜索函数。
的代码很容易写出,这里就不单独提供了(后面有完整代码),我们讲一下的写法:
首先带入的参数有些变化:
1. 增加了一个,代表另一个求解的数。
2. 将的意义改成的出现次数。
记忆化很容易写出,然后就是转移了:
由于这个数只由两个数字组成,我们不需要循环,只需要把循环展开就好了,即写两个,再把计算贡献的下来就了。(不过这里要记得考虑一下的转移)
long long dfs2(long long len,long long k1,long long k2,long long cnt1,long long cnt2,long long flg1,long long flg2){
if(len==0)
return cnt1==cnt2;
if(f[len][cnt1][cnt2][flg1][flg2]!=-1)
return f[len][cnt1][cnt2][flg1][flg2];
long long res=0;
if(k1<=num[len]||flg1==0)
res+=dfs2(len-1,k1,k2,cnt1+(k1!=0||flg2==0),cnt2,k1==num[len]&&flg1,k1==0&&flg2);
if(k2<=num[len]||flg1==0)
res+=dfs2(len-1,k1,k2,cnt1,cnt2+(k2!=0||flg2==0),k2==num[len]&&flg1,k2==0&&flg2);
return f[len][cnt1][cnt2][flg1][flg2]=res;
}
但是交上去后发现并没有这么简单,还是WA。
为什么呢?这里我想了很久,发现前导零没有考虑:因为位数不一定,因此要考虑前导零的转移(记得不要和与的转移重复计算贡献):
if(k1!=0&&k2!=0&&flg2==1)
res+=dfs2(len-1,k1,k2,cnt1,cnt2,num[len]==0&&flg1,flg2);
注意,这里数位dp采用更简单的记忆化搜索形式。
#include<stdio.h>
#include<string.h>
const int maxl=25;
long long i,j,k,m,n,len,ans;
long long num[maxl],f[maxl][maxl][maxl][2][2];
long long dfs1(long long len,long long k,long long cnt1,long long cnt2,long long flg1,long long flg2){
if(len==0)
return cnt1>=cnt2;
if(f[len][cnt1][cnt2][flg1][flg2]!=-1)
return f[len][cnt1][cnt2][flg1][flg2];
long long res=0;
for(long long i=0;i<=9;i++)
if(i<=num[len]||flg1==0)
res+=dfs1(len-1,k,cnt1+(i!=0||flg2==0)*(i==k),cnt2+(i!=0||flg2==0)*(i!=k),i==num[len]&&flg1,i==0&&flg2);
return f[len][cnt1][cnt2][flg1][flg2]=res;
}
long long calc1(long long x,long long k){
memset(f,-1,sizeof(f));
for(len=0;x;x/=10)
num[++len]=x%10;
return dfs1(len,k,0,0,1,1);
}
long long dfs2(long long len,long long k1,long long k2,long long cnt1,long long cnt2,long long flg1,long long flg2){
if(len==0)
return cnt1==cnt2;
if(f[len][cnt1][cnt2][flg1][flg2]!=-1)
return f[len][cnt1][cnt2][flg1][flg2];
long long res=0;
if(k1<=num[len]||flg1==0)
res+=dfs2(len-1,k1,k2,cnt1+(k1!=0||flg2==0),cnt2,k1==num[len]&&flg1,k1==0&&flg2);
if(k2<=num[len]||flg1==0)
res+=dfs2(len-1,k1,k2,cnt1,cnt2+(k2!=0||flg2==0),k2==num[len]&&flg1,k2==0&&flg2);
if(k1!=0&&k2!=0&&flg2==1)
res+=dfs2(len-1,k1,k2,cnt1,cnt2,num[len]==0&&flg1,flg2);
return f[len][cnt1][cnt2][flg1][flg2]=res;
}
long long calc2(long long x,long long k1,long long k2){
memset(f,-1,sizeof(f));
for(len=0;x;x/=10)
num[++len]=x%10;
return dfs2(len,k1,k2,0,0,1,1);
}
int main(){
scanf("%lld%lld",&n,&m);
for(i=0;i<=9;i++)
ans+=calc1(m,i)-calc1(n-1,i);
for(i=0;i<=9;i++)
for(j=i+1;j<=9;j++)
ans-=calc2(m,i,j)-calc2(n-1,i,j);
printf("%lld\n",ans);
return 0;
}