【数位DP】ZOJ2599Graduated Lexicographical Ordering

来源:互联网 发布:淘宝客服周总结报告 编辑:程序博客网 时间:2024/06/05 11:40

传送门
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 intusing 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;}
0 0
原创粉丝点击