题意:
将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;
}