URAL 2052 . Physical Education (数位DP + 二分)

题意:

将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;
}



你可能感兴趣的:(URAL 2052 . Physical Education (数位DP + 二分))