您的位置:首页 > 其它

【数位DP】ZOJ2599Graduated Lexicographical Ordering

2016-02-23 23:23 381 查看
传送门

Time Limit:10S Memory Limit:32768KB

Description

我们定义一种独特的给数排序的方法:

对于两个数,数码和较小的排在前面。因此120120排在44前面,44排在42294229前面。对于两个数码和一样的数,字典序小的排在前面。因此555555排在7878前面,2020排在200200前面。

现在给你NN个数1 N1~N,希望你对他们进行排序,然后求:kk排在第几个?第kk个是谁?

多组测试数据。

Input

若干行。每一行两个数N,kN,k含义如上。k<=N<=1018k<=N<=10^{18}。以00 00作为数据结尾。

Output

每一行两个数即两问的答案。

Sample Input

2020 1010

00 00

Sample Output

22 1414

一道很恶心的数位DP 555~

先考虑第一问。可以通过DP求出数位之和比kk小的数,对于数位之和相同的数,再计算字典序比kk小的数(通过枚举前缀来计算)。

对于第二问,已经知道了对于每一个数位和的个数就可以确定第kk小的数的数位之和。然后再枚举前缀就可以确定这个数了。

事实上这些都是很好想的,就是代码不太好写。

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

LL n, k, dp[20][205];

//数位之和是sum且有pos位的数的个数
LL dfs(int pos,int sum)
{
if(sum>9*pos||sum<0||pos<0)return 0;
if(!pos&&!sum)return 1;
if(dp[pos][sum])return dp[pos][sum];
LL &ans=dp[pos][sum];
for(int i=0;i<10&&i<=sum;++i)
ans+=dfs(pos-1,sum-i);
return ans;
}

//计算1~n中数位之和是sum的个数
LL dfs2(LL n,int sum)
{
int tmp[20], len=0;
while(n)
{
tmp[len++]=n%10;
n/=10;
}
LL ans=0;
for(int i=len-1;~i;--i)
for(int j=0;j<tmp[i];++j)
ans+=dfs(i,sum--);//从高位到低位枚举每一位的数字,同时数位之和减少
ans+=dfs(0,sum);//如果n的数位之和也是sum,就在这里+1
return ans;
}
//求n的数位之和
int getsum(LL n)
{
int ans=0;
while(n)ans+=n%10, n/=10;
return ans;
}
//求前缀为pre、数位之和为sum且在1~n之间的数的个数
LL ask_pre(LL n,LL pre,int sum)
{
LL ans=0;
int w[20], w2[20], len=0, len2=0;
while(n){w[len++]=n%10;n/=10;}
while(pre){w2[len2++]=pre%10;sum-=w2[len2-1];pre/=10;}
for(int i=0;i+i<len;++i)swap(w[i],w[len-i-1]);
for(int i=0;i+i<len2;++i)swap(w2[i],w2[len2-i-1]);
for(int i=0;i<len2;++i)
if(w[i]!=w2[i])
{
if(w[i]<w2[i])--len;
for(int j=len-len2;~j;--j)
ans+=dfs(j,sum);
return ans;
}
//说明前缀pre是n的前缀,那么就统计后面的位上能产生的个数
LL tmp=0;
for(int i=len2;i<len;++i)tmp=tmp*10+w[i];
ans+=dfs2(tmp,sum);
for(int i=len-len2-1;~i;--i)ans+=dfs(i,sum);
return ans;
}
//求字典序比k小的、数位之和为sum且1~n之间的数的个数
LL query(LL n,LL k,int sum)
{
int w[20], len=0;
LL ans=0, pre=1;
while(k)
{
w[len++]=k%10;
k/=10;
}
for(int i=len-1, b=1;~i;--i)
{
for(int j=b;j<w[i];++j)ans+=ask_pre(n,pre++,sum);
b=0;
pre*=10;
}
for(int i=0;i<len;++i)
if(w[i]==0)++ans;
else break;
return ans;
}
//求第一问的答案
LL ans1(LL n,LL k)
{
int sum=getsum(k);
LL ans=1;
for(int i=1;i<sum;++i)ans+=dfs2(n,i);
return ans+query(n,k,sum);
}
//求第二问的答案
LL ans2(LL n,LL k)
{
int sum=1;
LL tmp;
while((tmp=dfs2(n,sum))<k)
k-=tmp, ++sum;
LL pre=1;int pre_sum=1;
while(1)
{
while((tmp=ask_pre(n,pre,sum))<k)
{
k-=tmp;
++pre, ++pre_sum;
}
if(pre_sum==sum)break;
pre*=10;
}
while(--k)pre*=10;
return pre;
}

int main()
{
while(~scanf("%lld%lld",&n,&k)&&n)
printf("%lld %lld\n",ans1(n,k),ans2(n,k));
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: