题意:
将1到n ,进行一种特殊的排序,设SOD(x) 是 x 的所有数位上的数字之和,比如SOD(67)=6+7=13
排序原则:对于两数a,b.
若 SOD(a)不等于SOD(b) 则 SOD小的排在前面。
若SOD(a)等于SOD(b) 则数字小的排在前面。
比如,下面是1到19的排序结果:
1, 10, 2, 11, 3, 12, 4, 13, 5, 14, 6, 15, 7, 16, 8, 17, 9, 18, 19
问题是求排序前后的不动点有多少个,即问排序后,有多少个数满足A[i]=i
若n比较小,可以直接排序然后遍历。
但是这题n<=10^9 所以不能直接排序。
思路:数位DP+二分 很少写数位DP,所以写的比较乱
首先观察到,SOD的值最大是9*9=81.
所以先枚举 SOD
假设SOD小于当前SOD的数字之和为Sum,当前SOD的所有数字为A[1],A[2],...A[Q]
则需要求有多少个 T (1<= T <= Q) 使得Sum+T = A[ T ] ..... (1)
首先,易知 A[T+1] - A[ T ] >= 9 (因为SOD相同,那么除以9的余数必定相同,那么两数之差一定是9的倍数,而两数不相等,所以差>=9 )
(1)式中,随着T的增加,左侧每次加1,右侧每次加大于等于9的数,所以两函数最多有1个交点。
看出这一点之后,后面的思路就清晰了。
只需要二分答案,找到Sum+T >= A[ T ] 的最大T ,然后判断 Sum+T 是否等于 A[ T ] 就可以了。
二分需要两种类型的函数:
1.SOD=k 一共有多少个数。 ---FindAll 函数
2.SOD=k中的第 i 个数是什么。 ---Find 函数
FindAll 函数:
<span style="font-size:14px;">int FindAll(int t,int k,int flag);</span>
函数 t 表示当前位, k 表示数字之和 , 用flag表示当前位有没有受到限制.
当前位数字一次取遍0到9,若有限制,则不能超过n的当前位的数字。
然后递归求和。
Find函数思路也差不多。
有了这两个函数就可以二分答案了。复杂度:O(81*log2(n))
代码如下:
#include <iostream> #include <cstdio> #include <cmath> #include <cstring> #include <algorithm> #define inf 0x1fffffff #define FOR(i,n) for(long long (i)=1;(i)<=(n);(i)++) #define For(i,n) for(long long (i)=0;(i)<(n);(i)++) #define out(i) <<#i<<"="<<(i)<<" " #define OUT1(a1) cout out(a1) <<endl #define OUT2(a1,a2) cout out(a1) out(a2) <<endl #define OUT3(a1,a2,a3) cout out(a1) out(a2) out(a3)<<endl #define LL long long using namespace std; int g[11][91]; void Init(){//there are g[t][k] numbers that have t digits and // sum of digits is k (including leading zero) memset(g,0,sizeof(g)); g[0][0]=1; for(int t=1;t<=10;++t){ for(int k=0;k<=9*t;++k){ for(int i=0;i<=min(k,9);++i){ g[t][k]+=g[t-1][k-i]; } } } } int N,Dn,Digits[20]; int FindAll(int t,int k,int flag){//flag: is it bounded by N if(!flag||!t) return g[t][k]; int I=Digits[t],ANS=0; for(int i=0;i<=min(I,k);++i) ANS+=FindAll(t-1,k-i,i==I); return ANS; } int Dig[20]; void Find(int t,int k,int rank,bool flag){//the rank-th number in g[t][k] if(!t) return; if(!flag){ for(int i=0;i<=min(9,k);++i){ if(rank <= g[t-1][k-i]){ Dig[t]=i; Find(t-1,k-i,rank,false); return; } else rank-=g[t-1][k-i]; } } else{ int I=Digits[t],ANS=0; for(int i=0;i<=min(I,k);++i){ int Num=FindAll(t-1,k-i,i==I); if(rank<=Num) { Dig[t]=i; Find(t-1,k-i,rank,i==I); return; } else rank-=Num; } } } int main(void) { Init(); while(~scanf("%d",&N)){ Dn=0; while(N){ Digits[++Dn]=N%10; N/=10; } int Sum=0,ANS=0; for(int k=1;k<=81;++k){ int ALL=FindAll(Dn,k,true); if(!ALL) continue; int L,R,M,X; L=1;R=ALL+1;//[L,R) last Sum+i>=Value[i] while((L+1)^R){ M=(L+R)>>1; Find(Dn,k,M,true); X=0;for(int i=Dn;i;--i) X=X*10,X+=Dig[i]; if(Sum+M >=X) L=M; else R=M; } Find(Dn,k,L,true); X=0;for(int i=Dn;i;--i) X=X*10,X+=Dig[i]; ANS+=X==Sum+L; Sum+=ALL; } printf("%d\n",ANS); } return 0; }