题目大意:输入一个m,定义一个序列,其中 Xn = Xn - 1 + Xn - 2 + … + X1 + R, 其中 1 <= R <= m。输入a,b,m ,让你构造出以a为第一项,b为最后一项,满足上述要求的序列。1 <= a <= b <= 1e14, 1 <= m <= 1e14。
分析:项数不会超超过50项,假设 a = 1, b = 1e14, 每次 r = 0,最后一项为 2 ^ 49 次方,已经超过 1e14。
先看答案不存在的情况:由定义式子可得:
X1 = X1;
X2 = X1 + R1;
X3 = X2 + X1 + R2;
…
逐项代入得:Xn = pow(2,n - 2) * X1 + pow(2,n -3) * R1 + pow(2,n - 4) * R2 + … + 2 * Rn-3 + Rn-2 + Rn-1。
显然Xn的最小值是所有R都取1的情况,最大值是所有R都取m的情况:这时后面是一个等比数列求和,可以计算得到 最小值:Xn = pow(2,n - 2) * (a + 1),最大值 Xn = pow(2,n - 2) * (a + m)。
如果b 在这个值之间,就存在答案,可以用二分去搜索 n 使得最后一个 pow(2,n - 2) * (a + 1) <= b 成立。再判断一下 pow(2,n - 2) * (a + m) >= b 是否成立。
当有答案时:如何构造得到答案?
观察式子:Xn = pow(2,n - 2) * X1 + pow(2,n -3) * R1 + pow(2,n - 4) * R2 + … + 2 * Rn-3 + Rn-2 + Rn-1。
可以先取所有的R都等于某以个数值,这样Xn = pow(2,n - 2) * a + pow(2 , n - 2) * t,取Xn <= b 时t尽量大的值。之后所有的R只需要在当前这个序列上调整, 因为 pow(2,n - 2) * (a + 1) <= b <= pow(2,n - 2) * (a + m) , 所有的R取t后 R 和Xn的差值 肯定小于 pow(2,n - 2),Xn的一般式子中 后面一串刚好是一串最大位权到 pow(2,n -3)的二进制,二进制一定能表示出在这范围内的所有整数,所以一定能构造出。计算出b的二进制数值,每一位加上对应增量即可。
会爆long long,可以用除法和double
#include<bits/stdc++.h>
using namespace std;
int q;
long long m,a,b;
long long fpow(long long a,long long b) {
long long res = 1;
while(b) {
if(b & 1) res *= a;
a *= a;
b >>= 1;
}
return res;
}
long long rr[100];
long long pp[100];
int main() {
scanf("%d",&q);
while(q--) {
scanf("%lld%lld%lld",&a,&b,&m);
memset(pp,0,sizeof pp);
memset(rr,0,sizeof rr);
if(a == b) {
printf("1 %lld\n",a);
continue;
}
int l = 0,r = 51;
long long ans,res;
while(l < r) {
int mid = l + r >> 1;
long long tmp = fpow(2,mid);
if(tmp > b / (a + 1)) r = mid;
else {
l = mid + 1;
ans = mid;
}
}
if((a + m) < (double) b / fpow(2,ans)) {
puts("-1");
continue;
}
b -= fpow(2,ans) * a;
long long p = fpow(2,ans);
l = 1,r = m;
res = b / p;
b -= res * p;
for(int i = 1; i <= ans; i++) {
long long d = fpow(2,ans - i);
long long z = b / d;
b -= z * d;
rr[i] = z + res;
}
rr[ans + 1] = res;
pp[1] = a;
printf("%d ",ans + 2);
for(int i = 2; i <= ans + 2; i++) {
pp[i] = fpow(2,i - 2) * a;
for(int j = 1; j <= i - 2; j++) {
pp[i] += fpow(2,i - 2 - j) * rr[j];
}
pp[i] += rr[i - 1];
}
for(int i = 1; i <= ans + 2; i++) {
printf("%lld",pp[i]);
if(ans + 2 - i) printf(" ");
}
puts("");
}
return 0;
}
小心得:第二次做这种偏向数学思维的构造了,都是先构造出一个基本的,再去调整。