ZOJ 2599 Graduated Lexicographical Ordering (数位DP)

首先要吐两行槽:看到集训队论文上有这道题,由于数位DP一律写成记忆化搜索形式的强迫症,就没去看论文上的几个函数是什么……;结果被这道题虐的脑细胞死光……,最后是用随机数据对拍AC程序然后发现BUG改掉后才过掉的,花了一整天时间……。回头看论文,发现差不多……。

大概题意:给出一个long long范围的数N构成区间[1,N]和K(K<N),然后给出数的排名规则(参见原题),看到第一问求K的排名,稍稍考虑后觉得……这还用做?,第二问求第K大的数是什么,脑补N久之后感叹……这也能做?!  好吧,第一问是挺经典的数位统计问题,较简单,第二问,比较困难,很锻炼逆向思维。

思路:第一问大体解题轮廓:如果求出了位数为i,数位和为j的个数,然后把K按区间划分……,差不多是这样。细节处理:K的各位和为SUM,比SUM小的都加上,等于SUM字典序小于K的也加上……于是把比K排名小的数拆成这两部分计算会比较简单,设dp[i][j]为i位小于j的个数(含前导零),dp2[i][j]为i位等于j的个数(不含前导零)……,(其实dp2[i][j]可以用dp[i][j+1]-dp[i][j]表示,为了思路清晰就分开存了……,而且记忆化搜索懒惰求dp值的话,应该会出错……),第一部分区间划分比较简单,只需要考虑别超过N的限制就OK了,所以记忆化搜索只需要设一个标志f1:划分上界不超过N。第二部分繁琐些……,而且需要考虑当前是否有前导零(不明白的仔细考虑一下),觉得至少需要三个标志:f1:前导零标志,f2:划分上界不超过K(字典序限制),f3:划分上界不超过N……。这样就差不多了……

第二问大体解题轮廓:根据排名算数是不能按数位处理的……,所以要倒过来想:排名为K的数的SUM是多少?字典序又是如何的?  排名为K的SUM用第一问的第一个函数就能求出,枚举SUM的值判断是否大于K,大于K就停下,由于dp[i]数组的值是单调的,所以可以二分处理……。设SUM=X,那么第K个数就是SUM=X的所有数中字典序排名为(K-数位和<sum)的……,设这个排名为DX。然后这就又可以利用第一问的第二个函数去按位夹逼……,其实枚举每一位的值时也有单调性,可以二分,不过只枚举0~9,就没这必要了吧……,万一二分再写错……。细节注意:把SUM分配给每一位,SUM减到零时并不一定就是所求,可能还有后导零……。

不知道怎么说的更清楚点了……,还是多动脑想细节吧……。

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 
 5 using namespace std;
 6 typedef long long  ll;
 7 
 8 ll dp[22][222];
 9 ll dp2[22][222];
10 int d1[22],d2[22];
11 ll dfs(int w,int sum,bool f1)
12 {
13     if(sum<=0)return 0;
14     if(!w)return sum>0;
15     if(!f1&&~dp[w][sum])return dp[w][sum];
16     int e=f1?d1[w]:9;
17     ll ans=0;
18     for(int i=0;i<=e;++i)
19         ans+=dfs(w-1,sum-i,f1&&(i==e));
20     if(!f1)dp[w][sum]=ans;
21     return ans;
22 }
23 
24 ll dfs2(int w1,int w2,int sum,bool f1,bool f2,bool f3)
25 {
26     if(sum<0)return 0;
27     if(!w1)return sum==0;
28     if(!f2&&!f3&&~dp2[w1][sum])return dp2[w1][sum];
29     int e=f3?d1[w1]:9;
30     ll ans=0;
31     if(f2)e=min(e,d2[w2]);
32     for(int i=0;i<=e;++i)
33     {
34         if(!i&&f1)ans+=dfs2(w1-1,w2,sum,1,1,0);
35         else ans+=dfs2(w1-1,w2+1,sum-i,0,f2&&(i==d2[w2]),f3&&(i==d1[w1]));
36     }
37     if(!f2&&!f3)dp2[w1][sum]=ans;
38     return ans;
39 }
40 
41 ll cal(ll n,ll m)
42 {
43     int c1,c2,sum;
44     for(c1=0;n;d1[++c1]=n%10,n/=10);
45     for(c2=0,sum=0;m;d2[++c2]=m%10,sum+=d2[c2],m/=10);
46     d2[c2+1]=-1;
47     for(int i=0;i<c2/2;++i)swap(d2[i+1],d2[c2-i]);
48     return dfs(c1,sum,1)-1+dfs2(c1,1,sum,1,1,1);
49 }
50 ll cal2(ll n,ll k)
51 {
52     int c1,c2=1;
53     for(c1=0;n;d1[++c1]=n%10,n/=10);
54     int l=0,r=222,m=(l+r)>>1;
55     while(r>l)
56     {
57         if(dfs(c1,m,1)-1>=k)r=m;
58         else l=m+1;
59         m=(l+r)>>1;
60     }
61     // the kth sum is m-1
62     int sum=--m;
63     ll dx=k-(dfs(c1,m,1)-1);
64     memset(d2,-1,sizeof(d2));
65     while(sum>0)
66     {
67         for(d2[c2]=min(sum,9);d2[c2]>=0;--d2[c2])
68             if(dx>dfs2(c1,1,m,1,1,1))break;
69         sum-=d2[c2++];
70     }
71     if(dfs2(c1,1,m,1,1,1)!=dx)
72     {
73         bool flag=false;
74         if(c2>2&&d2[c2-1]==1&&d2[c2-2]<9)
75         {
76             ll t=d2[c2-1];
77             d2[--c2-1]+=t,d2[c2]=-1;
78             if(dfs2(c1,1,m,1,1,1)==dx)flag=true;
79             else d2[c2-1]-=t,d2[c2++]=t;
80         }
81         if(!flag)
82             do{d2[c2++]=0;}while(dfs2(c1,1,m,1,1,1)!=dx);
83     }
84     ll ans=0;
85     for(int i=1;i<c2;++i)
86         ans*=10,ans+=d2[i];
87     return ans;
88 }
89 int main()
90 {
91     ll n,m;
92     memset(dp,-1,sizeof(dp));
93     memset(dp2,-1,sizeof(dp2));
94     while(~scanf("%lld%lld",&n,&m)&&(n+m))
95         printf("%lld %lld
",cal(n,m),cal2(n,m));
96     return 0;
97 }
View Code
原文地址:https://www.cnblogs.com/mcflurry/p/3686145.html