kmjp's blog

競技プログラミング参加記です

TopCoder SRM 691 Div2 Hard Undiv2

ちょっと手間取った。
https://community.topcoder.com/stat?c=problem_statement&pm=14294

問題

正整数xに対し、f(x)とはxを割り切らない正整数のうち2番目に小さいものを言う。
正整数nに対し、f(1)~f(n)の和を求めよ。

解法

ためしにf(n)の最大値を求めると、n≦10^9の範囲では25が最大である。
g(a,b,n) := n以下の整数xで、xを割り切らない最初と2番目の正整数がaとbであるものの数
とする。解は \displaystyle \sum_{a=1}^{b-1} \sum_{b=2}^{25} b*g(a,b,n)となる。
あとはg(a,b,n)を求めよう。

a,bに対し、v:= 1~(b-1)からaを除いたもののLCM とする。
a,bのいずれかがvの約数である場合、g(a,b,n)=0である。
以下、それ以外の場合を考える。

a' := GCD(a,v)
b' := GCD(b,v)
とする。
g(a,b,n)に含まれるxはvの倍数であり、かつx/vがa'やb'の倍数でないものを列挙すればよい。
すなわち、p = n/vとすると、p以下のa'とb'どちらの倍数でもない数を数えればそれがg(a,b,n)。

class Undiv2 {
	public:
	
	long long getsum(int n) {
		int y,x,i;
		ll ret=0;
		for(y=1;y<=26;y++) for(x=1;x<y;x++) {
			ll v=1;
			ll x2=x,y2=y;
			for(i=1;i<y;i++) if(i!=x) v*=i/__gcd(v,(ll)i);
			x2/=__gcd(v,(ll)x);
			y2/=__gcd(v,(ll)y);
			if(v<=n) {
				int num[26*26]={};
				for(i=1;i<=x2*y2;i++) num[i]=num[i-1]+(((i%y2)>0)&&((i%x2)>0));
				ll di=n/v;
				ret += y*(di/(x2*y2)*num[x2*y2] + num[di%(x2*y2)]);
			}
		}
		
		return ret;
		
	}
}

まとめ

ちょっと迷ったけど解けて良かった。