yxc大佬现场翻车
dalao都是这样:我也不知道我怎么想的,反正我做对了
//来自算法基础课
数位统计dp
尤其强调分类讨论:
比如说我要找[1,abcdefg]中的数中1出现的个数
就得先求1在每一个位置上出现的次数
比如我要找第4位上出现的1的数有几个
就是要找满足 1 <= xxx1yyy <= abcdefg
- xxx∈[000,abc-1] , yyy∈[000,999] , ans += abc*1000
//如果前三位没填满,则后三位就可以随便填 - xxx==abc , yyy∈?
if(d<1) yyy不存在 , ans += 0
if(d==1) yyy∈[000,efg] , ans += efg+1
if(d>1) yyy∈[000,999] , ans += 1000
//如果前三位填满了,后三位怎么填取决于当前这一位
然后每一位上都是这么讨论的,最后累加起来就是总共出现的次数
这样就是求出来了[1,n]的
然后如果我想求[l,r]的用一下前缀和就搞定了
但是有一些特殊情况
1) x在第1位上出现的次数(不用考虑前半段):
bcdefg∈[00000,bcdefg] , ans += bcdefg+1
2) x在最后一位上出现的次数(不用考虑后半段):
//不知道为什么y老大没有说这种情况,但是我觉得应该讨论一下的吧
如果g<x,那么不存在这样的数,ans += 0
如果g==x,那么有一个这样的数,ans += 1
如果g>x,yyyyyy∈[000000,abcdef] , ans += abcdef+1
3) 如果我们枚举的数是0的话 :
//恶魔讨论
0不能在第一位
而且枚举到的这一位前面不能全是0,即
xxx∈[001,abc-1]
那当时直播的时候秦淮岸大佬说应该是从100到abc-1
因为我们说“不能有前导零”
正确理解“前导零”:
这个题里面,如果我要找一个数x在[1,n]中出现了几次
这里我们假设n是个五位数
要想找x在[1,n]中的一位数、两位数、三位数、四位数中出现的次数,
都是通过前面有那么一个没有实际作用的前导零来完成的
那么如果我们执着于“不能有前导零”,
则我们找出来的数只可能是个五位数,就不能正确完成了
说到这里,大家应该明白为什么从100到abc-1不对了吧
从100开始的话我们凑出来的数全是五位数啊
所以该有0的时候前面是可以有0的
我们在这里说的没有前导零,
指的是我们枚举x=0在哪一位上时,不考虑0做当前这个数的第一位
举个例子:
如果我们要找[1,1234]中0出现次数
那么首先0不可以做第一位
然后当0做第二位的时候,第一位不能是0
(因为第一位如果是0的话这个数就是00xx,还是不含有0的)
然后0做第三位的时候前两位不能同时为0
(因为前两位都是0那这个数就是000x,还是不含有0)
最后0做第4位的时候前三位不能是000
(因为这样最后这个数就是0,而我们要从[1,1234]中找)
这跟我们故意弄上去几个0当空气是有区别的,在询问0的个数的时候才有这种特殊情况
CODE
#include<bits/stdc++.h>
using namespace std;
const int N = 1e8+8;
int n,a,b;
int num[N];//建议不要用vector吧常数贼大
int get_num(int l,int r) { //算出数组num[]第r位到第l位的数是多少
int ans = 0;
for(register int i=l; i>=r; i--)
ans = ans*10 + num[i];
return ans;
}
int pow10(int i) { //求10的i次方
int ans = 1;
while(i--) ans *= 10;
return ans;
}
int work(int n,int x) {
if(n==0) return 0;//虽然a和b不会取到0,但是a-1会
int len = 0;
while(n) {
num[++len] = n%10;
n /= 10;
}
int ans = 0;
if(x!=0) { //x非0
for(register int i=len; i>0; i--) {
if(i<len) ans += get_num(len,i+1) * pow10(i-1);
//第一类讨论,x在首位上时不讨论这种情况
if(num[i]==x) ans += get_num(i-1,1)+1;
else if(num[i]>x) ans += pow10(i-1);
else ans += 0;
//第二类讨论
}
} else { //x为0
for(register int i=len-1; i>0; i--) {
ans += (get_num(len,i+1)-1) * pow10(i-1);
//x不会在首位,所以也不用担心首位特判
if(num[i]==x) ans += get_num(i-1,1)+1;
else if(num[i]>x) ans += pow10(i-1);
else ans += 0;
//0对第二类讨论没有影响
}
}
return ans;
}
int main() {
while(scanf("%d%d",&a,&b) && a!=0 && b!=0) {
if(a>b) swap(a,b);//输入不保证后面的比前面的大
for(register int i=0; i<=9; i++) {
int ans = work(b,i) - work(a-1,i);
printf("%d ",ans);
}
printf("\n");
}
return 0;
}
上面那个work()函数是为了大家好理解那么写的
其实可以合并一下代码更简洁
int work(int n,int x) {
int len = 0;
while(n) {
num[++len] = n%10;
n /= 10;
}
int ans = 0;
for(register int i=len-!x; i>0; i--) {
if(i<len) {
if(x) ans += get_num(len,i+1) * pow10(i-1);
else ans += (get_num(len,i+1)-1) * pow10(i-1);
}
//第一类讨论,x在首位上时不讨论这种情况
//x不会在首位,所以也不用担心首位特判
if(num[i]==x) ans += get_num(i-1,1)+1;
else if(num[i]>x) ans += pow10(i-1);
else ans += 0;
//第二类讨论
}
return ans;
}
ok
随手单击左边向上三角点赞谢谢您嘞
“x在最后一位上出现的次数”写的有问题,不需要考虑:
(1)xxxxxx∈[000000,abcdef-1]时ans += abcdef
(2)xxxxxx==abcdef:
1)g < x,ans += 0
2)g >= x,ans += 1
均包含在前面的分类里(相当于yyy∈[000,000],不需要考虑yyy有多少种)
考虑x在最后一位,g == x 时候的情况,[000000 ~ abcdef - 1]x ans += abcdef
g > x 的情况,ans += abcdef + 1 包含在上述的讨论里面,故可以不考虑
“比如我要找第4位上出现的1的数有几个
就是要找满足 1 <= xxx1yyy <= abcdefg
if(d>1) yyy∈[000,999] , ans += 1000”
上为作者原文中的话,我不是很懂想请教一下
你要 求出 第四位是1的数有几个,但是你d都大于1了 第四位就不是1了,为什么还 ans+1000 不是直接为0,或者不用讨论吗?
d是指abcdefg里的d
嗷嗷,懂了,谢谢佬,那这样的话if(d==1) yyy∈[000,efg] , ans += efg+1
if(d>1) yyy∈[000,999] , ans += 1000 xxx1 123∈[000,efg]&& xxx1 123∈[000,999],(写的不太好,我意思就是yyy如果小于efg并且小于999 那不加重了吗? 加了两次)
abcdefg是个确定的数,不管等于大于小于就只有一种情况,(应该是这样,我也刚开始学hhh)
在这里关于0的情况我从len而不是从len-1开始怎么也可以过呢
orz 感谢大佬终于懂了前导0的含义了
但是没有dp哎
nums数组没必要开那么大吧,N=10就够了·
我靠,懂了懂了
orz
感谢大佬
醍醐灌顶, 感谢!
orz
“要想找x在[1,n]中的一位数、两位数、三位数、四位数中出现的次数,
都是通过前面有那么一个没有实际作用的前导零来完成的”
这句话很精辟啊,前导0讲的很好,代码也很清晰,谢谢大佬
register int 这里如果不用的话会不会tle
这个比较好理解
if(d>1) yyy∈[000,999] , ans += 1000这个应该加上ans+=(d-1)*1000把
前导零这里分析写的很好!
orz,终于看懂了
%%%%%%%%
懂了