题解 P1066 【2^k进制数】组合+进制+高精压位

wjyyy

2018-04-27 19:14:16

Solution

这是一道很数学的题(楼下也有用模拟递推跑的),从题目意思来看,就是要求有多少个长为$w$的二进制($01$)串满足在被转为$2^k$进制之后,满足左边的数严格小于右边。 如下表是一种$k=3,w=7$满足条件的情况 | 二进制 | 1 | 100 | 101 | | -----------: | -----------: | -----------: | -----------: | | **$2^3$进制** | **1** | **4** | **5** | | 二进制位数 |7|6~4|3~1| |八进制位数|3|2|1| ||最高位||最低位| 题目表达的条件有 1. 转换之后的数至少有$2$位,则$r_{(10)}≥2^k$。 1. 二进制串长度为$w$,但不代表第$w$位上一定是$1$,因此这个二进制串可以是$2-w$的任意长度,所以根据长度来枚举。 1. 如果$w∤(2^k)$,那么这个数的最高位不能取遍$[1,2^k-1]$(不能重复算取$0$的情况,因为取$0$相当于没有最高位,无意义)。 因此我们来看一下当$k=3,w>8$时这种情况从低位向高位处理的过程。 | |… | 第3位 | 第2位 | 第1位 | | ----------- | ----------- | ----------- | ----------- | | 可以取到的数的个数 |…| $C^3_7$* | $C^2_7$ | $N/A$ | | 说明 |…|取3个分别占1,2,3位** | 八进制有7个数,取两个分别占1,2位*** | 不能取1位 | | 总共取到的数的个数 |…| $C^3_7+C^2_7$ | $C^2_7$ |$N/A$ | 注释: *每个组合里一个数只可能出现一次,保证了严格单增的条件 **每个组合只算了一次,因为每个组合有且仅有一种顺序使其单增 ***因为单增,只能取[1,7],注意不能取0,也不能取8,因为8要进位 那么如果$w\leq8$呢 我们回到一开始举的例子 $k=3,w=7$ | 包含位数| 1 | 3 | 3 | | -----------: | -----------: | -----------: | -----------: | | 取值范围 | {$1$} | $[1,2^3-1]$ | $[1,2^3-1]$ | 这个情况下,最高位只能取1,那么后面的数依照严格单增只能取$[2,2^3-1]$ 所以能取到的数有$6$个,{$2,3,4,5,6,7$} 在原来的基础上加上$C^2_6$ 其中$2=\lceil \frac {(w-1)}k\rceil+1$ $6=2^k-1-1$ 同时因为~~高精除法太烦人了~~时间效率问题,我们可以提前把用到的$C_i^j$利用杨辉三角存一下,并且$imax=2^k$这样高精就只用写加法了。 上面提到$imax=2^k$,但实际上数组层数开的不是$2^9$($kmax=9$),而是$2^10$,因为杨辉三角会多那么一层。但根据这个题的测试点,开$600$即可AC 正常的杨辉三角要注意,当使用$C_i^j$时,要调用$a[i+1][j+1]$,也可以提前从$[0][0]$处理一下 ## Code: ```cpp #include<cstring> #include<cstdio> int max(int x,int y){return x>y?x:y;} struct lll//高精要压位,不然拖低时间效率,这个题计算量也很大 { int num[52]; lll(int x) { memset(num,0,sizeof(num)); if(x==0) return; int k=0; while(x) { k++; num[k]=x%10000; x/=10000; } num[0]=k; } lll() { memset(num,0,sizeof(num)); } friend lll operator +(lll a,lll b) { a.num[0]=max(a.num[0],b.num[0])+1; for(int i=1;i<=a.num[0];i++) { a.num[i]+=b.num[i]; if(a.num[i]>=10000) { a.num[i+1]++; a.num[i]-=10000; } } while(a.num[0]>0&&a.num[a.num[0]]==0) a.num[0]--; return a; } }; lll a[620][620]; void print(lll x) { for(int i=x.num[0];i>0;i--) { if(i!=x.num[0]) { if(x.num[i]<1000) printf("0"); if(x.num[i]<100) printf("0"); if(x.num[i]<10) printf("0"); } printf("%d",x.num[i]); } printf("\n"); } int main() { int k,w; scanf("%d%d",&k,&w); int t=(w-1)/k+1;//t是转化为2^k进制数后的位数(包括不确定的一位) lll one(1); for(int i=1;i<=(1<<k)+1;i++)//为了杨辉三角的调用,需要多做一层 { a[i][1]=one; a[i][i]=one; for(int j=2;j<i;j++) a[i][j]=a[i-1][j-1]+a[i-1][j]; } /*for(int i=1;i<=(1<<k)+1;i++) { for(int j=1;j<=i;j++) { print(a[i][j]); printf(" "); } printf("\n"); }*///这里是打印杨辉三角,检验是否正确 lll sum(0); for(int i=2;i<t;i++) sum=sum+a[(1<<k)-1 +1][i +1];//+1是杨辉三角性质的要求 //是(1<<k)-1个里选2个而不是8个里选,要格外注意 w=(w-1)%k+1; for(int i=1;i<(1<<w);i++) sum=sum+a[(1<<k)-i-1 +1][t-1 +1];// print(sum); return 0; } ```