PKU演習問メモ(4/14)
No. | Title | 分類・解法 | 主観的難易度 |
---|---|---|---|
3604 | Professor Ben | 整数問題 | ★★★☆☆ |
2273 | An Excel-lent Problem | 実装問題・数学 | ★★☆☆☆ |
3112 | Pie | 二分法 | ★★☆☆☆ |
以下解説など。
3604 Professor Ben
問題概要
自然数nに対して、以下の値を求めよ。
Σ(iの約数の個数)^3, for ∀i∈{nの約数}
ただし与えられるnの個数は50万個以下、nは500万以下である。
例:6
6の約数は1,2,3,6
1の約数の個数の3乗は1^3,
2の約数の個数の3乗は2^3,
3の約数の個数の3乗は2^3,
6の約数の個数の3乗は4^3,
となり求める数は1+8+8+64=81
解法
nを素因数分解してn=p1^q1 p2^q2 p3^q3 ...
と表せたとすると、nの約数を全て生成するには
0≦a1≦q1,0≦a2≦q2, ...となるような自然数a1,a2, ...を全て選べば良い。
これには再帰を使うのが自然であるが、
ここでm=r1^s1 r2^s2 ...と素因数分解されるようなある数mの約数の個数は
(1+s1)(1+s2)...個であることに注意する。
すると約数を生成すると同時に(1+a1)(1+a2)...の積を計算しておけば、
約数の約数の個数(typoじゃないよ)も同時に計算できることがわかる。
よってこれを全て足してやれば求める数となる。
以下のコードでは素因数分解の高速化のために1から500万以下の全ての整数について、あらかじめその「最初の素因数」をエラトステネスの篩の応用で求めておいてみた。また、出現する可能性のある3乗の計算もあらかじめ行っておいた。
(多分テストケースが50万個なので効果はあるはず)
ソースコード
const int MX=5000001; int ffct[MX],T[1200]; int factor[20],nf; int rec(int c,int s) { if(c==nf)return T[s]; int ret=0; rep(i,factor[c])ret+=rec(c+1,s*(i+1)); return ret; } int solve(int n) { nf=0; while(n>1) { int c=0,f=ffct[n]; while(ffct[n]==f)n/=f,c++; factor[nf++]=c+1; } return rec(0,1); } int main() { ffct[1]=1; for(int i=2;i*i<MX;i++)if(!ffct[i]) { ffct[i]=i; for(int j=i+i;j<MX;j+=i)if(!ffct[j])ffct[j]=i; } for(int i=(int)sqrt(MX);i<MX;i++)if(!ffct[i])ffct[i]=i; rep(i,1200)T[i]=i*i*i; int q,n; scanf("%d",&q); while(q--) { scanf("%d",&n); printf("%d\n",solve(n)); } return 0; }
2273 An Excel-lent Problem
問題概要
表計算ソフトにおいて"R3C1"のようなR+行番号C+列番号の形で与えられたセルの座標を、エクセルで使われる座標系に変換せよ。
エクセルの座標系は"A3"のような列を表す文字列+行番号の形式である。
列番号はA,B,C,...,Zと続き、Z列の次の列はAAである。更にAA,AB,...AZ,BA,BB,...,ZZと続き、ZZ列の次の列はAAAであり、以下も同様とする。
解法
行番号は変換する必要がないので列について考える。
まずは列を表す文字が何文字になるかについて考察する。
1文字で表すことの出来る列は26列、2文字では26^2列...であるので、
Σ26^iがその次で列番号を超えるようなnが、文字数である。
行番号からΣ26^iの差をとれば、n桁のAAA...Aの並びから求める文字列が何番目にあるかがわかるが、これはAを0とみた26進数の変換に等しい。
以上の考察を実装する。
ソースコード
int main() { string str; while(1) { cin>>str; fr(i,str)if(*i=='R'||*i=='C')*i=' '; stringstream ss(str); int a,b; ss>>a>>b; if(a==0&&b==0)break; b--; int len=1; for(int t=26;b>=t;t*=26)b-=t,len++; char ans[30]={0}; rep(i,len)ans[i]='A'; for(int i=0;b;b/=26,i++)ans[i]+=b%26; reverse(ans,ans+len); cout<<ans<<a<<endl; } return 0; }
3112 Pie
問題概要
半径r[i],高さ1のn個の円柱形をしたパイをf人の友達と自分に同じ大きさずつ分ける。
ただし、パイ片は一人二つ以上になってはならず、そのために無駄になるパイが出てもよい。
このとき一人分として取りうる最大のパイの量を求めよ。
解法
大きさaのパイ片をがいくつ取れるかは各パイの体積をaで割った商(小数点未満切り捨て)を足すことで求められる。
aを二分法により変化させながらf+1個のパイ片を取れる最大のaを求めればよい。
ただし誤差が結構厳しいので注意。
ソースコード
int n,f,sz[10000]; int getp(double a) { int r=0; rep(i,n) r+=int(sz[i]/a); return r; } int main() { int cs; cin>>cs; while(cs--) { cin>>n>>f; double s=0; rep(i,n)cin>>sz[i],sz[i]*=sz[i],s+=sz[i]; double lo=0,hi=s/f,mid; while(hi-lo>0.000001) { mid=(lo+hi)/2; if(getp(mid)>=f+1)lo=mid; else hi=mid; } printf("%.4f\n",mid*3.141592653589793); } return 0; }