传送门
Time Limit:10S Memory Limit:32768KB
Description
我们定义一种独特的给数排序的方法:
对于两个数,数码和较小的排在前面。因此 120 排在 4 前面, 4 排在 4229 前面。对于两个数码和一样的数,字典序小的排在前面。因此 555 排在 78 前面, 20 排在 200 前面。
现在给你 N 个数 1 N ,希望你对他们进行排序,然后求: k 排在第几个?第 k 个是谁?
多组测试数据。
Input
若干行。每一行两个数 N,k 含义如上。 k<=N<=1018 。以 0 0 作为数据结尾。
Output
每一行两个数即两问的答案。
Sample Input
20 10
0 0
Sample Output
2 14
一道很恶心的数位DP 555~
先考虑第一问。可以通过DP求出数位之和比 k 小的数,对于数位之和相同的数,再计算字典序比 k 小的数(通过枚举前缀来计算)。
对于第二问,已经知道了对于每一个数位和的个数就可以确定第 k 小的数的数位之和。然后再枚举前缀就可以确定这个数了。
事实上这些都是很好想的,就是代码不太好写。
#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;
}