今天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
搜索
最新評論

|
|