今天FangGG讲了数位类统计的。感觉记住那个图就行了。O((logn)^2)的初始化,O(logn)的查询
感觉是利用了相同规模的树完全相同,避免重复计算,用f[i][j]记录下来。 f[i][j]表示一棵高度为i的完全二叉树内二进制表示中恰好含有j个1的数的个数。不包括根的。感觉那个根是虚设的 叶子的i为0....所以那个i就是二进制下的第i位(从0开始) 论文只计算到i=31,我觉得这样子只对于正数,第31位他认为为0了 。而负数的为1,所以我对于负数的算到i=32 ural 1057 ★★★ 不同的幂 数位统计 B进制同样可以用二进制解决!
/**//* 论文《浅谈数位类统计问题》 题意: 求给定区间[X,Y]中满足下列条件的整数个数:这个数恰好等于K 个互不相等的B的整数次幂之和。 例如,设X=15,Y=20,K=2,B=2,则有且仅有下列三个数满足题意: 17 = 24+20, 18 = 24+21, 20 = 24+22。 一个数x = aiB^i + ai-1B^(i-1)++a1B+a0。如果ai>1,则表示出来的x肯定包含两个以上的B^i 互不相等的幂之和,亦即其B进制表示的各位数字都只能是0和1! 我们只需讨论二进制的情况,其他进制都可以转化为二进制求解。 用论文给出的方法初始化O((logn)^2) 查询O(logn)
★★★ 对于询问n,我们需要求出不超过n的最大B进制表示只含0、1的数: 找到n 的左起第一位非0、1 的数位,将它变为1,并将右面所有数位设为1。(因为大于1的肯定不可取,后面置为1使它最接近原来的数) 将得到的B进制表示视为二进制进行询问即可。 */ #include<cstdio>
int f[32][32]; int X,Y,K,B;
void init() { f[0][0]=1; for(int i=1;i<=31;i++)//f[i]是不包含根的 { f[i][0]=f[i-1][0]; for(int j=1;j<=i;j++) f[i][j]=f[i-1][j]+f[i-1][j-1]; } } int calc(int x,int k)//统计[0..x]内二进制表示含k个1的数的个数 { int ans = 0,tot =0;//tot记录当前路径上已有的1的数量,ans表示答案 for(int i=31;i;i--)//就是走前缀吧 需要31虽然是正数,但下面的(1<<(i-1))<=x要用到 { if(x&(1<<i)) { tot++; if(tot>k)break; x^=(1<<i); } if((1<<(i-1))<=x)ans+=f[i-1][k-tot]; } if(x+tot==k)++ans;// return ans; }
int change(int x) { int p=1,tot=0; //注意溢出 while(x>=(long long)p*B)p*=B,tot++; int ans =0; while(p&&x/p<=1)ans+=x/p*(1<<tot),tot--,x%=p,p/=B; ans+=(1<<(tot+1))-1; return ans; } int main() { init(); scanf("%d%d%d%d",&X,&Y,&K,&B); printf("%d\n",calc(change(Y),K)-calc(change(X-1),K)); return 0; }
spoj 1182 数位统计的做法★★★ 再二分 负数看成补码!
/**//* 论文《浅谈数位类统计问题》 题意:将区间[m,n]内的所有整数按照其二进制表示中1 的数量从小到大排序。 如果1 的数量相同,则按照数的大小排序。 m*n>=0 求这个序列中的第k个数。其中,负数使用补码来表示
依次统计区间[m,n]内二进制表示中含1的数量为0,1,2,…的数,直到累加的答案超过k, 则当前值就是答案含1的个数,假设是s。利用例一的算法可以解决这个问题。 接下来二分答案即可。判断[m,n]内1的个数为s的,逼近k' 需要特别处理下0 使m,n非0 */ #include<cstdio> #include<cstring>
int f[33][33];
void init() { f[0][0]=1; for(int i=1;i<=32;i++) { f[i][0]=f[i-1][0]; for(int j=1;j<=i;j++) f[i][j]=f[i-1][j-1]+f[i-1][j]; } } int calc(int x,int k) { int ans=0,tot=0; for(int i=32;i;i--)//我是虚设一个根在第32位处,其实溢出了 { if(i!=32&&(x&(1<<i))) { tot++; if(tot>k)break; x^=(1<<i); } if(x&(1<<(i-1)))//我还是写成&吧,怕负数的话用>=有问题 虽然这题没问题 ans+=f[i-1][k-tot];//f[i]不含根i } if(x+tot==k)++ans; return ans; } int main() { init(); int T; for(scanf("%d",&T);T--;) { int m,n,k; scanf("%d%d%d",&m,&n,&k); //0的特殊处理 if(m==0) { if(k==1){printf("0\n");continue;} m++; k--; } if(n==0) { if(k==1){printf("0\n");continue;} n--; k--; } int s=1,tmp;//s从1开始!
//观察那个图,m-1负溢出也不怕 while(tmp=calc(n,s)-calc(m-1,s),tmp<k)k-=tmp,s++; int L=m,R=n; while(R>L) { int M=(R+L)>>1; if(calc(M,s)-calc(m-1,s)>=k)R=M; else L=M+1;//M之前的肯定不可能咯 } printf("%d\n",R); } return 0; }
SPOJ 2319 这题搞了好久,醉死。从TLE 搞到 30s 再搞到5s多。。。 贴个图。。
/**//* * 《浅谈数位类统计问题》例3 题意:将0,1,2^k-1 这些数分成M份,每份的值为该份的1的数字之和 ,求使最大的那一份最小 K <=100 ,M<=100 最大值最小化,二分的经典应用 二分这个最大值X,然后看能不能组成M份 ,然后再调整 而计算[0,S]的1的个数,可以先预处理 cntOne[k] 表示长度为k的01串中1的个数,则 cntOne[k] = 2cntOne[k-1] + 2^(k-1)
二分的判断函数chk的过程如下: lastResult = 0 上一次的结果 for(次数cnt<=M) lastResult+=X 这一次的估计值 if(lastResult >= cntOne[K]) return true; lastResult = find(lastResult); 找到最接近lastResult的确切值
而find(X) 函数就是一种逼近的过程 最大值为X 找[0,S]的1的总和最接近X的 我没有用论文的方法,而是不断地减X,直至最接近0了
用这种方法挺快的!JAVA 中rank 1 */
package SPOJ;
import java.math.BigInteger; import java.util.Scanner;
public class _2319 {
static int K,M; static BigInteger pow2[],cntOne[]; static BigInteger ZERO = BigInteger.ZERO,ONE = BigInteger.ONE,TWO = BigInteger.valueOf(2);
static BigInteger find(BigInteger X) { BigInteger ans = X,tmp; int k=K-1,one=0; while(k>0&&X.compareTo(ZERO)>0) { tmp = cntOne[k].add(pow2[k].multiply(BigInteger.valueOf(one))); if(X.compareTo(tmp)>=0) { X=X.subtract(tmp); one++; } k--; } if(X.compareTo(BigInteger.valueOf(one))>=0) X=X.subtract(BigInteger.valueOf(one)); return ans.subtract(X); }
static boolean chk(BigInteger X) { // System.out.println("X: "+X); BigInteger lastResult = ZERO; for(int cnt=0;cnt<M;cnt++) { lastResult = lastResult.add(X); if(lastResult.compareTo(cntOne[K])>=0)return true; lastResult = find(lastResult); } return false;//cnt>M }
public static void main(String[] args) { Scanner cin = new Scanner(System.in); K = cin.nextInt(); M = cin.nextInt(); pow2 = new BigInteger[K+1]; cntOne = new BigInteger[K+1]; pow2[0] = ONE; cntOne[0] = ZERO; for(int i=1;i<=K;i++) { pow2[i] = pow2[i-1].shiftLeft(1); cntOne[i] = cntOne[i-1].shiftLeft(1).add(pow2[i-1]); } BigInteger L = ONE,R = cntOne[K],Mid; int cnt = 0; while(R.compareTo(L)>0)//R>L { Mid = R.add(L).divide(TWO); if(chk(Mid)) R = Mid; else L=Mid.add(ONE); } System.out.println(R); } }
|
|
常用链接
随笔分类
Links
搜索
最新评论
|
|