POJ 2800 : Joseph\'s Problem (须仔细分析)

(寒假马拉松第一场 K题)

在题目中有三种情况:

11<k<n

2k == n ;

3k>n;

对于第一种情况我们可以分为1kkn两个子问题来解。

1.11k

     for( i = 1 , sum = 0 ; i <= k ; i ++ ) sum += k %i ;

1.2kn sum =(n-k)*k;

而对于第二种情况就是第一种情况的(1)。但是就这样写的话时明显的tle的。

对于第二种情况也可以分为几个小问题来求解:

2.11k/2

          for( i = k/2 ; i >=1 ; i-- ) sum+= k % i ;

2.2k/2k:

k%k+((k-1)+1)%(k-1)+.......+ (k-m0)+m0)%(k-m0) 其中m0 < k –m0 , m0<k/2, 又因为m0 是整数所以m0 = k/2 – 1 ;

简单的来说:如果k%i ( i ++ )  只要 k/i (i ++ ) 的值相同 , 则 k % i 是以个等差数列 例如:

  

  k = 100 , i = 26

  100 % 26 = 22   100 / 26 = 3

  100 % 27 = 19   100 / 27 = 3

  100 % 28 = 16   100 / 28 = 3

  100 % 29 = 13   100 / 29 = 3

所以 sum = sum(2.1) + sum(2.2) 

然而2.1k=10^9时所需的时间也是很大的,所以继续优化:

同理可得到for( i=k/2;i>=1;i-- ) 也可以化简成:

2.1.1( (k/2)*2+m1 )%(k/2)+((k/2-1)*2+m1+2)%(k/2-1)+...+((k/2-x)*2+m1+2*x)%(k/2-x);

这里的m1=k%(k/2),因为在2.2式子中的m0=k/2-1 ,  m1 = k%(k/2) ,x=k/6-1;

2.1.1的式子是公差为2的等差数列;

2.1.2for(i=k/3;i>=1;i--)
sum+=k%i;

综上可以知道,对于原来的线性搜索便可以拆成若干个等差数列的和。由此可以将式子化简成:

(k%(k/1)+k%(k/2+1))*(k/1-k/2)/2+(k%(k/2)+k%(k/3+1))*(k/2-k/3)/2+…s=k/i;e=k/(i-1);

则上诉式子就变为: sum+=(k%e+k%(s+1))*(e-s)/2; 而它的时间复杂度仅为sqrt(k);

对于第三种情况,既k>n的情况,是第二种情况的特殊情况,区别就是把首相变成k%n而已。

3.11<n<sqrt(k);

3.21<sqrt(k)<n;

 

 

     

所以综合上面的可以知道,我们可以把sum分为三部分:

1、 sum+=(k%e+k%(s+1))*(e-s)/2

2、for(i=1;i<=n&&i<=b;i++) sum+=k%i; 其中b = k / sqrt(k) ;

3、 sum += (n-k)*k ;

2800Accepted692K157MSG++480B

#include<iostream>
#include<cstdio>
#include<cmath>
#define LL long long
using namespace std;
LL n,k;
LL solve(LL n,LL k){
	LL ans=0,a=(LL)sqrt((double)k),b=k/a,s,t,i;
	if(n>k)
		ans+=(n-k)*k;
	for(i=a;i>1;i--){
		s=k/i;
		t=k/(i-1);
		if(s>n) break;
		if(t>n) t=n;
		ans+=(t-s)*(k%t+k%(s+1))/2;
	}
	if(n<b) b=n;
	for(i=1;i<=b;i++)
		ans+=k%i;
	return ans;
}
int main(){
	while(~scanf("%lld%lld",&n,&k)){
		printf("%lld\n",solve(n,k));
	}
	return 0;
}


另法:http://blog.csdn.net/lencle/article/details/6257452
这个题目貌似很简单。就是求 sum(k % i),其中 1 <= i <= n;给定 k 和 n,求这个和。
当然可以直接算,不过会超时,因为 k 和 n 可能很大。
当 k >= n 时,应该将商分组,组中的余数是连续的,可以利用等差数列直接求和。比如 k = 36,n = 17 时,分为以下几组:
36/1;
36/2;
36/3;
36/4;
36/5,
36/6;
36/7;
36/8, 36/9;
36/10, 36/11, 36/12;
36/13, 36/14, 36/15, 36/16, 36/17。

#include <iostream>
using namespace std;
int n,k;
__int64 cal(int n,int k) {
        __int64 ans=0;
        int low,high,next,d,i;
        if (n>k) {
                 ans=(__int64)(n-k)*k;
                 n=k;     //还有n位未处理
        } 
        d=k/n;     //商
        while (n>1) {
              next=k/(d+1);
              //cout << n << ' ' << next << endl;
              if (n==next) {
                             ans+=(__int64)(k%n);
                             n--;
                             d=k/n;
                             continue;
              }
              
              low=k%n;
              high=k%(next+1);
              ans+=(__int64)(low+high)*(n-next)/2;
              n=next;
              d++;
        }
        return ans;
}
int main() {
    cin >> n >> k;
    cout << cal(n,k) << endl;
    system("pause");
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值