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\n",cal(n,m),cal2(n,m)); 96 return 0; 97 }