题目
构造出一个正整数序列
{
a
1
,
a
2
,
.
.
.
,
a
m
}
\{a_1,a_2,...,a_m\}
{a1,a2,...,am},满足:
∑
i
=
1
m
=
n
\sum_{i=1}^m=n
∑i=1m=n
a
i
≥
a
i
+
1
∗
p
q
a_{i}\geq a_{i+1}*\frac{p}{q}
ai≥ai+1∗qp
其中
n
n
n和
p
,
q
p,q
p,q是给定的,
m
m
m自己定。
求最大的
∑
i
=
1
n
a
i
x
k
\sum_{i=1}^n a_ix^k
∑i=1naixk
n
,
p
,
q
≤
1
e
9
n,p,q\leq 1e9
n,p,q≤1e9
k
≤
1
e
6
k\leq 1e6
k≤1e6
正解
没有思考历程,因为比赛的时候几乎没有想过这题,也不存在一点思路。
这题是乱搞题。
记
d
=
p
q
d=\frac{p}{q}
d=qp,按照
d
d
d的大小分类讨论:
当
d
≤
1
d\leq1
d≤1时,最优的构造方法是
a
i
=
1
a_i=1
ai=1。
这个给出结论之后就可以很好地感受出来。如果问我为什么是这样,我只能回答无可奉告。
然后就变成了求自然数幂和。
直接套拉格朗日插值法公式,可以做到
O
(
k
)
O(k)
O(k)或
O
(
k
lg
k
)
O(k\lg k)
O(klgk)(快速幂).。
当
d
>
1
d>1
d>1时,
显然
a
i
>
a
i
+
1
a_i>a_{i+1}
ai>ai+1,所以
m
≤
2
n
m\leq \sqrt{2n}
m≤2n
考虑一个厉害的贪心策略:找到尽量靠后的,并且可以加一的
a
i
a_i
ai。判断是否可以加一,就是在
a
i
a_i
ai加一之后,根据题目的性质往前调整所有
j
<
i
j<i
j<i的
a
j
a_j
aj的值,如果增量不超过
n
n
n,就可以成立。如果可以成立,就给它加一,前面的也跟着调整,
n
n
n的值减小。一直循环着做下去直到
n
n
n清零。
可以如此感受:不考虑题目的限制,给后面的数字加一,比起给前面的数字加一是更优的;然后,给尽量后的数字加一,有利于扩展到更优的情况。设想你给 a i a_i ai加一,由于题目限制 a i − 1 a_{i-1} ai−1加 x x x,这个肯定比直接给 a i − 1 a_{i-1} ai−1加 x x x优。对于更多的数量关系,也可以类似地考虑。
很显然,如果
a
i
a_i
ai可以加一,则满足
j
<
i
j<i
j<i的所有
a
j
a_j
aj都可以加一。
于是这个位置可以二分出来。
每次都加一太慢,于是二分一下最多可以加多少。
什么?担心 a i a_i ai加若干次之后,存在 j > i j>i j>i满足 a j a_j aj可以加一?
别想了,如果这样 a j a_j aj之前早就加一了。
至于时间复杂度分析,呃呃呃看题解吧……
代码
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cassert>
#define ll long long
#define mo 1000000007
#define M 1000010
ll qpow(ll x,ll y=mo-2){
ll r=1;
for (;y;y>>=1,x=x*x%mo)
if (y&1)
r=r*x%mo;
return r;
}
int n,m,k,p,q;
double d;
int a[M];
bool judge(int x,int y){
if (y>n)
return 0;
ll ai=a[x]+y,need=y;
for (int i=x;i>1;--i){
ll ai_1=ceil(ai*d);
need+=ai_1-a[i-1];
if (need>n)
return 0;
ai=ai_1;
}
return 1;
}
void add(int x,int y){
a[x]+=y;
n-=y;
for (int i=x;i>1;--i){
ll ai_1=ceil(a[i]*d);
n-=ai_1-a[i-1];
a[i-1]=ai_1;
}
}
ll fac[M];
ll s[M];
int pri[M],np;
bool inp[M];
ll pro(ll l,ll r){
ll p=1;
for (ll i=l;i<=r;++i)
p=p*(i%mo)%mo;
return (p+mo)%mo;
}
int main(){
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
fac[0]=1;
for (int i=1;i<=1000001;++i)
fac[i]=fac[i-1]*i%mo;
int T;
scanf("%d",&T);
while (T--){
scanf("%d%d%d%d",&n,&k,&p,&q);
if (p<=q){
s[0]=0,s[1]=1;
np=0;
memset(inp,0,sizeof(bool)*(k+1));
for (int i=2;i<=k+1;++i){
if (!inp[i]){
pri[++np]=i;
s[i]=qpow(i,k);
}
for (int j=1;j<=np && i*pri[j]<=k+1;++j){
inp[i*pri[j]]=1;
s[i*pri[j]]=s[i]*s[pri[j]]%mo;
if (i%pri[j]==0)
break;
}
}
for (int i=1;i<=k+1;++i)
(s[i]+=s[i-1])%=mo;
if (n<=k+1){
printf("%lld\n",s[n]);
continue;
}
ll sn=0;
for (int i=0;i<=k+1;++i)
sn+=qpow((-(i-k-1)&1?mo-1:1)*fac[-(i-k-1)]%mo*fac[i]%mo*(n-i)%mo)%mo*s[i]%mo;
sn=sn%mo*pro(n-k-1,n)%mo;
printf("%lld\n",sn);
continue;
}
d=(double)p/q;
m=sqrt(2*n);
memset(a,0,sizeof(int)*(m+1));
while (n){
int l=1,r=m,x=1,y=1;
while (l<=r){
int mid=l+r>>1;
if (judge(mid,1))
l=(x=mid)+1;
else
r=mid-1;
}
l=1,r=n;
while (l<=r){
int mid=l+r>>1;
if (judge(x,mid))
l=(y=mid)+1;
else
r=mid-1;
}
add(x,y);
}
ll ans=0;
for (int i=1;i<=m;++i)
ans+=a[i]*qpow(i,k)%mo;
ans%=mo;
printf("%lld\n",ans);
}
return 0;
}
总结
比赛时要敢于贪心啊……