幸运数字 (数位DP)

没有题目链接,数据网上也没有。粘一下题面。

【题目描述】

中国人喜欢数字68。特别地,一些人喜欢满足含有特定个数68的数。现在请求出,在区间[L,R]之间的第K大的含有X6Y8的数。

【输入】

输入的第一行包括4个数字,L,R,X,Y

接下来的一行给出该组数据的询问数Q

接下来Q行中,每行有一个整数K

【输出】

对于某个询问,输出一行,为对应的第K大的数。如果不存在这个数则输出“That's too bad!”

【输入样例】

1 1000 1 1

10

1

2

3

4

5

6

7

8

9

100

【输出样例】

68

86

168

186

268

286

368

386

468

That's too bad!

【数据范围】

  对于30%的数据,1<=L<=R<=100000

  对于100%的数据,1<=L<=R<=10^18

对于100%的数据,1<=X,Y<=18, 1<=Q<=30

好像就是水数位DP(Orz)。

先粘一下过了30%数据的搜索版。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<map>
using namespace std;
#define LL long long

const int maxn=20;

LL L,R,X,Y;
int cnt=0;

map<LL,int>q;
int limit[maxn];
LL ans[1000000];

inline LL read(){
	LL x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}

void ask(int x,int nx,int ny,int fp,int col,LL num){
	if(x==0){
		if(nx==X && ny==Y){
			if(!col)q[num]=1;
			if(col && q[num]!=1)ans[++cnt]=num;
		}
		return ;
	}
	
	int f=fp?limit[x]:9;
	for(int i=0;i<=f;i++){
		if(i==6)ask(x-1,nx+1,ny,i==limit[x] && fp,col,num*10+i);
		else if(i==8)ask(x-1,nx,ny+1,i==limit[x] && fp,col,num*10+i);
		else ask(x-1,nx,ny,i==limit[x] && fp,col,num*10+i);
	}
}

void find(LL x,int col){
	int tot=0;
	while(x){
		limit[++tot]=x%10; x/=10;
	}
	ask(tot,0,0,1,col,0);
}

int main(){
	L=read(); R=read(); X=read(); Y=read();
	find(L,0);
	find(R,1);
	int Q; scanf("%d",&Q);
	for(int i=1;i<=Q;i++){
		LL K=read();
		if(K>cnt)printf("That's too bad!\n");
		else printf("%I64d\n",ans[(int)K]);
	}
	return 0;
}

再粘一个100%的数据的递推版。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define LL long long

const int maxn=20;

LL L,R;
int X,Y,len;
int c[maxn][maxn],a[maxn];

void init(){//预处理,数组c[i][j]表示长度为i含有j个符合条件的数的方案数
	for(int i=0;i<20;i++){
		c[i][i]=1; c[i][0]=1;
	}
	for(int i=2;i<20;i++)
		for(int j=1;j<i;j++){
			c[i][j]=c[i-1][j]+c[i-1][j-1];
		}
}

void limit(LL N){//求数N的每一位的限制
	len=0;
	while(N){
		a[len++]=N%10; N/=10;
	}
	reverse(a,a+len);
}

LL power(int x,int y){//快速幂
	LL ret=1;
	for(LL i=x;y;y>>=1,i*=i){
		if(y&1)ret*=i;
	}
	return ret;
}

LL get_sum(int pos,int x,int y){
	if(x<0 || y<0)return 0;
	if(pos<x+y)return 0;
	return c[pos][x+y]*c[x+y][x]*power(8,pos-x-y);
/*
方案数为:pos个数中选x+y个6和8,然后在x+y个数中选x个6,这样6和8就选好了。剩下的就只需选除去6和8的数
那就是8^(pos-x-y),快速幂求出。
*/
}

LL get(LL N){
	limit(N);
	LL ret=0;
	int x=X,y=Y;
	for(int i=0;i<len && x>-1 && y>-1;i++){
		if(a[i]<7)ret+=(a[i])*get_sum(len-i-1,x,y);
		else if(a[i]<9)ret+=(a[i]-1)*get_sum(len-i-1,x,y)+get_sum(len-i-1,x-1,y);
		else ret+=(a[i]-2)*get_sum(len-i-1,x,y)+get_sum(len-i-1,x-1,y)+get_sum(len-i-1,x,y-1);
		x-=(a[i]==6); y-=(a[i]==8);
	}
	return ret;
}

LL get_num(LL N){
	LL num=0,ret=0;
	int pos=0,x=X,y=Y;
	while(get_sum(pos,X,Y)<N)pos++;
	for(int i=0;i<pos;i++){
		int j;
		for(j=0;j<9 && num+get_sum(pos-i-1,x-(j==6),y-(j==8))<N;j++){
			num+=get_sum(pos-i-1,x-(j==6),y-(j==8));
		}
		x-=(j==6); y-=(j==8);
		ret=ret*10+j;
	}
	return ret;
}

int main(){
	init();
	scanf("%I64d%I64d%d%d",&L,&R,&X,&Y);
	LL lx=get(L); LL ly=get(R+1);
	int Q; scanf("%d",&Q);
	for(int i=1;i<=Q;i++){
		LL temp; scanf("%I64d",&temp);
		if(temp+lx>ly)printf("That's too bad!\n");
		else printf("%I64d\n",get_num(temp+lx));
	}
	return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章