找规律是最强的
本蒟蒻不会数位DP,不会数学方法,决定重拾起自己小学时学习的——找规律!!!
先上结构体:
struct Count { int a[10]; Count operator + (const Count x) const { Count ans; memset(ans.a,0,sizeof(ans.a)); for(int i=0;i<10;++i) { ans.a[i]=a[i]+x.a[i]; } return ans; } Count operator - (const Count x) const//重载运算符 { Count ans; memset(ans.a,0,sizeof(ans.a)); for(int i=0;i<10;++i) { ans.a[i]=a[i]-x.a[i]; } return ans; } };
首先我们需要一个暴力,以便寻找规律:
#include <cstdio> #include <algorithm> using namespace std; #define ll long long ll n,ans[10]; void work(ll a){ while(a>0){ ans[a%10]++; a/=10; } } int main(void){ scanf("%lld",&n); for(ll i=1;i<=n;i++){ work(i); } for(ll i=0;i<=9;i++){ printf("%lld\n",ans[i]); } }
接下来请跟我在程序里输入几个数
"9,19,29,99,199,299"
答案如下:
" 9: 0 1 1 1 1 1 1 1 1 1" " 19: 1 12 2 2 2 2 2 2 2 2" " 29: 2 13 13 3 3 3 3 3 3 3" " 99: 9 20 20 20 20 20 20 20 20 20" "199:29 140 40 40 40 40 40 40 40 40" "299:49 160 160 60 60 60 60 60 60 60"
有没有发现:
9到19,1多了11个,而其他的多了1个
19到29,2多了11个,而其他的多了1个
99到199,1多了120个,而其他的多了20个
199到299,2多了120个,而其他的多了20个
如果在多试几组的话更明显,但本蒟蒻先将规律写出:
//对于任意一个:a*10^(l-1)-1;//l即位数 //其每一种数字加上:pow(10,l-2)*(l-1);//每一位上加的 //其首位a的种类额外加上:pow(10,l-1); int len(int x)//计算位数 { int k=0; while(x>0) { ++k; x/=10; } return k; } int plus(int x)//计算每一个a*10^(l-1)-1到下一个同样满足要求的所要加上的值 { int lx=len(x),llx=len(x+1); if(lx!=llx) { return p[lx]; } return p[lx-1]; } //main函数分隔线 p[0]=1; p[1]=1;//千万别用pow,不稳定 for(int i=2;i<=len(n);++i) { p[i]=p[i-1]*10; for(int j=0;j<10;++j) { s[i].a[j]=p[i-2]*(i-1);//计算每一位需要加上的值 } } int l=9,li; for(int i=19;i<=n;i+=plus(i)) { li=len(i); ans=ans+s[li]; ans.a[i/p[li-1]]+=p[li-1];//计算a额外加上的值 l=i;//下文提到的记录运行位置的变量 }
然后,因为题目不可能直接给我们特殊的n,所以我们需要一个变量记录我们运行到了那里,然后用暴力弥补与n之间的差距。
这里有一个小优化,就是可以比较到底是当前运行到的i接近呢,还是下一个i接近?以减少运行次数。
Count each(int x)//暴力程序 { Count ans; memset(ans.a,0,sizeof(ans.a)); while(x>0) { ++ans.a[x%10]; x/=10; } return ans; } //main函数分隔线 if(n-l<l+plus(l)-n)//进行比较 { for(int i=l+1;i<=n;++i)//如果小的近,则一个个加上 { ans=ans+each(i); } } else { l+=plus(l); li=len(l); ans=ans+s[li]; ans.a[l/p[li-1]]+=p[li-1]; for(int i=n+1;i<=l;++i)//如果大的近,则先将l变为下一个,再一个个减去 { ans=ans-each(i); } }
主要思路到这里就结束了,最后将答案输出就可以AC了(数据太水)
注:细心的oier可能已经发现了,前面的暴力其实是叫大佬帮我写的
附完整代码:
#include<cstdio> #include<cstring> #define int long long struct Count { int a[10]; Count operator + (const Count x) const { Count ans; memset(ans.a,0,sizeof(ans.a)); for(int i=0;i<10;++i) { ans.a[i]=a[i]+x.a[i]; } return ans; } Count operator - (const Count x) const { Count ans; memset(ans.a,0,sizeof(ans.a)); for(int i=0;i<10;++i) { ans.a[i]=a[i]-x.a[i]; } return ans; } }; Count each(int x) { Count ans; memset(ans.a,0,sizeof(ans.a)); while(x>0) { ++ans.a[x%10]; x/=10; } return ans; } int len(int x) { int k=0; while(x>0) { ++k; x/=10; } return k; } int n; int p[10]; Count s[10]; Count ans; int plus(int x) { int lx=len(x),llx=len(x+1); if(lx!=llx) { return p[lx]; } return p[lx-1]; } signed main() { freopen("count.in","r",stdin); freopen("count.out","w",stdout); scanf("%lld",&n); if(n<=9) { for(int i=1;i<=n;++i) { ans=ans+each(i); } } else { for(int i=1;i<=9;++i) { ans=ans+each(i); } } p[0]=1; p[1]=10; for(int i=2;i<=len(n);++i) { p[i]=p[i-1]*10; for(int j=0;j<10;++j) { s[i].a[j]=p[i-2]*(i-1); } } int l=9,li; for(int i=19;i<=n;i+=plus(i)) { li=len(i); ans=ans+s[li]; ans.a[i/p[li-1]]+=p[li-1]; l=i; } if(n-l<l+plus(l)-n) { for(int i=l+1;i<=n;++i) { ans=ans+each(i); } } else { l+=plus(l); li=len(l); ans=ans+s[li]; ans.a[l/p[li-1]]+=p[li-1]; for(int i=n+1;i<=l;++i) { ans=ans-each(i); } } for(int i=0;i<10;++i) { printf("%lld\n",ans.a[i]); } }
2018-10-01