Time Limit: 9000/3000 MS (Java/Others) Memory Limit: 65535/32768 K (Java/Others)
Total Submission(s): 322 Accepted Submission(s): 114
思路:我们可以发现循环节为a,b的最小公倍数tmp,所以我们只要求从1到tmp的花费即可,易知a和b的大小关系对题目答案没有影响,不妨设a>b,设从1到tmp的花费依次为 w1,w2,w3,....wtmp,我们可以将这tmp个值分成每b个一组,一共a/gcd组(gcd为a和b的最大公约数),我们设num[i]为第i组的和,现在的关键问题是num[i]怎么求,对于每一组num[i],这时第一个数在a盒子中的第po个,那么它的花费就是po-1(因为这个数一定是在b盒子的第一个),那么对于后面的第2个,第三个。。。。。。的花费也将是po-1,直到走到第po+b-1个,或者到第a个盒子(其实就是a和po+b-1去个小),若走到了第po+b-1个盒子,则这一组已经算完,为b*(po-1)花费,且po将被更新为po+b,否则,还应该计算剩下来的花费,这个和前一半类似,只不过这是a盒子从1开始,b盒子从a-po+2开始而已,这也是一串相同的花费,可以O(1)求出,然后更新po。将所有组求完后,剩下的工作就简单了。这里不再罗嗦。
以下是代码。
#include <iostream> #include <stdio.h> #include <string.h> #include <algorithm> #define ll long long using namespace std; ll dp[100010],sum[100010]; int Po[100010]; long long GCD(long long a,long long b) { if(b==0) return a; return GCD(b,a%b); } ll getans(ll x) { if(x>0) return x; return -x; } int main() { //freopen("dd.txt","r",stdin); int ncase; scanf("%d",&ncase); while(ncase--) { memset(dp,0,sizeof(dp)); memset(sum,0,sizeof(sum)); ll n,a,b; cin>>n>>a>>b; if(a<b) swap(a,b); if(a==b) printf("0\n"); else { ll tmp=GCD(a,b); ll tt=tmp; tmp=a*b/tmp; dp[1]=0; sum[1]=0; ll po=b+1; Po[1]=1; Po[2]=b+1; for(int i=2;i<=a/tt;i++) { if(po+b-1<=a) { dp[i]=(po-1)*b; po=po+b; if(po>a) po-=a; } else { dp[i]=(po-1)*(a-po+1); int tt=a-po+2; dp[i]+=(tt-1)*(b-tt+1); po=b-tt+2; if(po>a) po-=a; } Po[i+1]=po; sum[i]=sum[i-1]+dp[i]; } ll ans=0; ans=sum[a/tt]*(n/tmp); n%=tmp; // cout<<n<<endl; ll x=n/b; ans+=sum[x]; n%=b; if(n) { ll PO=Po[x+1]; for(int i=1;i<=n;i++) { ans+=getans(PO-i); PO++; if(PO>a) PO-=a; } } cout<<ans<<endl; } } return 0; }